FontRenderer.cpp revision 9777173eb6c9eb97c7921c8288ebc65e3ab3ce6f
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 "Caches.h"
29
30namespace android {
31namespace uirenderer {
32
33///////////////////////////////////////////////////////////////////////////////
34// Defines
35///////////////////////////////////////////////////////////////////////////////
36
37#define DEFAULT_TEXT_CACHE_WIDTH 1024
38#define DEFAULT_TEXT_CACHE_HEIGHT 256
39#define MAX_TEXT_CACHE_WIDTH 2048
40#define TEXTURE_BORDER_SIZE 2
41
42#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16)
43
44///////////////////////////////////////////////////////////////////////////////
45// CacheTextureLine
46///////////////////////////////////////////////////////////////////////////////
47
48bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
49    if (glyph.fHeight + TEXTURE_BORDER_SIZE > mMaxHeight) {
50        return false;
51    }
52
53    if (mCurrentCol + glyph.fWidth + TEXTURE_BORDER_SIZE < mMaxWidth) {
54        *retOriginX = mCurrentCol + 1;
55        *retOriginY = mCurrentRow + 1;
56        mCurrentCol += glyph.fWidth + TEXTURE_BORDER_SIZE;
57        mDirty = true;
58        return true;
59    }
60
61    return false;
62}
63
64///////////////////////////////////////////////////////////////////////////////
65// Font
66///////////////////////////////////////////////////////////////////////////////
67
68Font::Font(FontRenderer* state, uint32_t fontId, float fontSize,
69        int flags, uint32_t italicStyle, uint32_t scaleX,
70        SkPaint::Style style, uint32_t strokeWidth) :
71        mState(state), mFontId(fontId), mFontSize(fontSize),
72        mFlags(flags), mItalicStyle(italicStyle), mScaleX(scaleX),
73        mStyle(style), mStrokeWidth(mStrokeWidth) {
74}
75
76
77Font::~Font() {
78    for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
79        if (mState->mActiveFonts[ct] == this) {
80            mState->mActiveFonts.removeAt(ct);
81            break;
82        }
83    }
84
85    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
86        delete mCachedGlyphs.valueAt(i);
87    }
88}
89
90void Font::invalidateTextureCache(CacheTextureLine *cacheLine) {
91    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
92        CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i);
93        if (cacheLine == NULL || cachedGlyph->mCachedTextureLine == cacheLine) {
94            cachedGlyph->mIsValid = false;
95        }
96    }
97}
98
99void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
100        uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
101    int nPenX = x + glyph->mBitmapLeft;
102    int nPenY = y + glyph->mBitmapTop;
103
104    int width = (int) glyph->mBitmapWidth;
105    int height = (int) glyph->mBitmapHeight;
106
107    if (bounds->bottom > nPenY) {
108        bounds->bottom = nPenY;
109    }
110    if (bounds->left > nPenX) {
111        bounds->left = nPenX;
112    }
113    if (bounds->right < nPenX + width) {
114        bounds->right = nPenX + width;
115    }
116    if (bounds->top < nPenY + height) {
117        bounds->top = nPenY + height;
118    }
119}
120
121void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
122        uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
123    int nPenX = x + glyph->mBitmapLeft;
124    int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
125
126    float u1 = glyph->mBitmapMinU;
127    float u2 = glyph->mBitmapMaxU;
128    float v1 = glyph->mBitmapMinV;
129    float v2 = glyph->mBitmapMaxV;
130
131    int width = (int) glyph->mBitmapWidth;
132    int height = (int) glyph->mBitmapHeight;
133
134    mState->appendMeshQuad(nPenX, nPenY, u1, v2,
135            nPenX + width, nPenY, u2, v2,
136            nPenX + width, nPenY - height, u2, v1,
137            nPenX, nPenY - height, u1, v1, glyph->mCachedTextureLine->mCacheTexture);
138}
139
140void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
141        uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
142    int nPenX = x + glyph->mBitmapLeft;
143    int nPenY = y + glyph->mBitmapTop;
144
145    uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
146    uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
147
148    CacheTexture *cacheTexture = glyph->mCachedTextureLine->mCacheTexture;
149    uint32_t cacheWidth = cacheTexture->mWidth;
150    const uint8_t* cacheBuffer = cacheTexture->mTexture;
151
152    uint32_t cacheX = 0, cacheY = 0;
153    int32_t bX = 0, bY = 0;
154    for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
155        for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
156#if DEBUG_FONT_RENDERER
157            if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
158                ALOGE("Skipping invalid index");
159                continue;
160            }
161#endif
162            uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
163            bitmap[bY * bitmapW + bX] = tempCol;
164        }
165    }
166}
167
168void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
169        SkPathMeasure& measure, SkPoint* position, SkVector* tangent) {
170    const float halfWidth = glyph->mBitmapWidth * 0.5f;
171    const float height = glyph->mBitmapHeight;
172
173    float nPenX = glyph->mBitmapLeft;
174    vOffset += glyph->mBitmapTop + height;
175
176    const float u1 = glyph->mBitmapMinU;
177    const float u2 = glyph->mBitmapMaxU;
178    const float v1 = glyph->mBitmapMinV;
179    const float v2 = glyph->mBitmapMaxV;
180
181    SkPoint destination[4];
182    measure.getPosTan(x + hOffset + nPenX + halfWidth, position, tangent);
183
184    // Move along the tangent and offset by the normal
185    destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
186            -tangent->fY * halfWidth + tangent->fX * vOffset);
187    destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset,
188            tangent->fY * halfWidth + tangent->fX * vOffset);
189    destination[2].set(destination[1].fX + tangent->fY * height,
190            destination[1].fY - tangent->fX * height);
191    destination[3].set(destination[0].fX + tangent->fY * height,
192            destination[0].fY - tangent->fX * height);
193
194    mState->appendRotatedMeshQuad(
195            position->fX + destination[0].fX,
196            position->fY + destination[0].fY, u1, v2,
197            position->fX + destination[1].fX,
198            position->fY + destination[1].fY, u2, v2,
199            position->fX + destination[2].fX,
200            position->fY + destination[2].fY, u2, v1,
201            position->fX + destination[3].fX,
202            position->fY + destination[3].fY, u1, v1,
203            glyph->mCachedTextureLine->mCacheTexture);
204}
205
206CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
207    CachedGlyphInfo* cachedGlyph = NULL;
208    ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
209    if (index >= 0) {
210        cachedGlyph = mCachedGlyphs.valueAt(index);
211    } else {
212        cachedGlyph = cacheGlyph(paint, textUnit);
213    }
214
215    // Is the glyph still in texture cache?
216    if (!cachedGlyph->mIsValid) {
217        const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit);
218        updateGlyphCache(paint, skiaGlyph, cachedGlyph);
219    }
220
221    return cachedGlyph;
222}
223
224void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
225        int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
226    if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
227        render(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
228                bitmapW, bitmapH, NULL, NULL);
229    } else {
230        render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL,
231                0, 0, NULL, NULL);
232    }
233}
234
235void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
236            int numGlyphs, int x, int y, const float* positions) {
237    render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL,
238            0, 0, NULL, positions);
239}
240
241void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
242        int numGlyphs, SkPath* path, float hOffset, float vOffset) {
243    if (numGlyphs == 0 || text == NULL || len == 0) {
244        return;
245    }
246
247    text += start;
248
249    int glyphsCount = 0;
250    SkFixed prevRsbDelta = 0;
251
252    float penX = 0.0f;
253
254    SkPoint position;
255    SkVector tangent;
256
257    SkPathMeasure measure(*path, false);
258    float pathLength = SkScalarToFloat(measure.getLength());
259
260    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
261        float textWidth = SkScalarToFloat(paint->measureText(text, len));
262        float pathOffset = pathLength;
263        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
264            textWidth *= 0.5f;
265            pathOffset *= 0.5f;
266        }
267        penX += pathOffset - textWidth;
268    }
269
270    while (glyphsCount < numGlyphs && penX <= pathLength) {
271        glyph_t glyph = GET_GLYPH(text);
272
273        if (IS_END_OF_STRING(glyph)) {
274            break;
275        }
276
277        CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
278        penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
279        prevRsbDelta = cachedGlyph->mRsbDelta;
280
281        if (cachedGlyph->mIsValid) {
282            drawCachedGlyph(cachedGlyph, roundf(penX), hOffset, vOffset, measure, &position, &tangent);
283        }
284
285        penX += SkFixedToFloat(cachedGlyph->mAdvanceX);
286
287        glyphsCount++;
288    }
289}
290
291void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
292        int numGlyphs, Rect *bounds) {
293    if (bounds == NULL) {
294        ALOGE("No return rectangle provided to measure text");
295        return;
296    }
297    bounds->set(1e6, -1e6, -1e6, 1e6);
298    render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, NULL);
299}
300
301void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
302        int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
303        uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
304    if (numGlyphs == 0 || text == NULL || len == 0) {
305        return;
306    }
307
308    static RenderGlyph gRenderGlyph[] = {
309            &android::uirenderer::Font::drawCachedGlyph,
310            &android::uirenderer::Font::drawCachedGlyphBitmap,
311            &android::uirenderer::Font::measureCachedGlyph
312    };
313    RenderGlyph render = gRenderGlyph[mode];
314
315    text += start;
316    int glyphsCount = 0;
317
318    if (CC_LIKELY(positions == NULL)) {
319        SkFixed prevRsbDelta = 0;
320
321        float penX = x + 0.5f;
322        int penY = y;
323
324        while (glyphsCount < numGlyphs) {
325            glyph_t glyph = GET_GLYPH(text);
326
327            // Reached the end of the string
328            if (IS_END_OF_STRING(glyph)) {
329                break;
330            }
331
332            CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
333            penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
334            prevRsbDelta = cachedGlyph->mRsbDelta;
335
336            // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
337            if (cachedGlyph->mIsValid) {
338                (*this.*render)(cachedGlyph, (int) floorf(penX), penY,
339                        bitmap, bitmapW, bitmapH, bounds, positions);
340            }
341
342            penX += SkFixedToFloat(cachedGlyph->mAdvanceX);
343
344            glyphsCount++;
345        }
346    } else {
347        const SkPaint::Align align = paint->getTextAlign();
348
349        // This is for renderPosText()
350        while (glyphsCount < numGlyphs) {
351            glyph_t glyph = GET_GLYPH(text);
352
353            // Reached the end of the string
354            if (IS_END_OF_STRING(glyph)) {
355                break;
356            }
357
358            CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
359
360            // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
361            if (cachedGlyph->mIsValid) {
362                int penX = x + positions[(glyphsCount << 1)];
363                int penY = y + positions[(glyphsCount << 1) + 1];
364
365                switch (align) {
366                    case SkPaint::kRight_Align:
367                        penX -= SkFixedToFloat(cachedGlyph->mAdvanceX);
368                        penY -= SkFixedToFloat(cachedGlyph->mAdvanceY);
369                        break;
370                    case SkPaint::kCenter_Align:
371                        penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1);
372                        penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1);
373                    default:
374                        break;
375                }
376
377                (*this.*render)(cachedGlyph, penX, penY,
378                        bitmap, bitmapW, bitmapH, bounds, positions);
379            }
380
381            glyphsCount++;
382        }
383    }
384}
385
386void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
387    glyph->mAdvanceX = skiaGlyph.fAdvanceX;
388    glyph->mAdvanceY = skiaGlyph.fAdvanceY;
389    glyph->mBitmapLeft = skiaGlyph.fLeft;
390    glyph->mBitmapTop = skiaGlyph.fTop;
391    glyph->mLsbDelta = skiaGlyph.fLsbDelta;
392    glyph->mRsbDelta = skiaGlyph.fRsbDelta;
393
394    uint32_t startX = 0;
395    uint32_t startY = 0;
396
397    // Get the bitmap for the glyph
398    paint->findImage(skiaGlyph);
399    mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY);
400
401    if (!glyph->mIsValid) {
402        return;
403    }
404
405    uint32_t endX = startX + skiaGlyph.fWidth;
406    uint32_t endY = startY + skiaGlyph.fHeight;
407
408    glyph->mStartX = startX;
409    glyph->mStartY = startY;
410    glyph->mBitmapWidth = skiaGlyph.fWidth;
411    glyph->mBitmapHeight = skiaGlyph.fHeight;
412
413    uint32_t cacheWidth = glyph->mCachedTextureLine->mCacheTexture->mWidth;
414    uint32_t cacheHeight = glyph->mCachedTextureLine->mCacheTexture->mHeight;
415
416    glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
417    glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
418    glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
419    glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
420
421    mState->mUploadTexture = true;
422}
423
424CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) {
425    CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
426    mCachedGlyphs.add(glyph, newGlyph);
427
428    const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph);
429    newGlyph->mGlyphIndex = skiaGlyph.fID;
430    newGlyph->mIsValid = false;
431
432    updateGlyphCache(paint, skiaGlyph, newGlyph);
433
434    return newGlyph;
435}
436
437Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize,
438        int flags, uint32_t italicStyle, uint32_t scaleX,
439        SkPaint::Style style, uint32_t strokeWidth) {
440    Vector<Font*> &activeFonts = state->mActiveFonts;
441
442    for (uint32_t i = 0; i < activeFonts.size(); i++) {
443        Font* font = activeFonts[i];
444        if (font->mFontId == fontId && font->mFontSize == fontSize &&
445                font->mFlags == flags && font->mItalicStyle == italicStyle &&
446                font->mScaleX == scaleX && font->mStyle == style &&
447                (style == SkPaint::kFill_Style || font->mStrokeWidth == strokeWidth)) {
448            return font;
449        }
450    }
451
452    Font* newFont = new Font(state, fontId, fontSize, flags, italicStyle,
453            scaleX, style, strokeWidth);
454    activeFonts.push(newFont);
455    return newFont;
456}
457
458///////////////////////////////////////////////////////////////////////////////
459// FontRenderer
460///////////////////////////////////////////////////////////////////////////////
461
462static bool sLogFontRendererCreate = true;
463
464FontRenderer::FontRenderer() {
465    if (sLogFontRendererCreate) {
466        INIT_LOGD("Creating FontRenderer");
467    }
468
469    mGammaTable = NULL;
470    mInitialized = false;
471    mMaxNumberOfQuads = 1024;
472    mCurrentQuadIndex = 0;
473
474    mTextMeshPtr = NULL;
475    mCurrentCacheTexture = NULL;
476    mLastCacheTexture = NULL;
477    mCacheTextureSmall = NULL;
478    mCacheTexture128 = NULL;
479    mCacheTexture256 = NULL;
480    mCacheTexture512 = NULL;
481
482    mLinearFiltering = false;
483
484    mIndexBufferID = 0;
485
486    mSmallCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
487    mSmallCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
488
489    char property[PROPERTY_VALUE_MAX];
490    if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
491        if (sLogFontRendererCreate) {
492            INIT_LOGD("  Setting text cache width to %s pixels", property);
493        }
494        mSmallCacheWidth = atoi(property);
495    } else {
496        if (sLogFontRendererCreate) {
497            INIT_LOGD("  Using default text cache width of %i pixels", mSmallCacheWidth);
498        }
499    }
500
501    if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) {
502        if (sLogFontRendererCreate) {
503            INIT_LOGD("  Setting text cache width to %s pixels", property);
504        }
505        mSmallCacheHeight = atoi(property);
506    } else {
507        if (sLogFontRendererCreate) {
508            INIT_LOGD("  Using default text cache height of %i pixels", mSmallCacheHeight);
509        }
510    }
511
512    sLogFontRendererCreate = false;
513}
514
515FontRenderer::~FontRenderer() {
516    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
517        delete mCacheLines[i];
518    }
519    mCacheLines.clear();
520
521    if (mInitialized) {
522        delete[] mTextMeshPtr;
523        delete mCacheTextureSmall;
524        delete mCacheTexture128;
525        delete mCacheTexture256;
526        delete mCacheTexture512;
527    }
528
529    Vector<Font*> fontsToDereference = mActiveFonts;
530    for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
531        delete fontsToDereference[i];
532    }
533}
534
535void FontRenderer::flushAllAndInvalidate() {
536    if (mCurrentQuadIndex != 0) {
537        issueDrawCommand();
538        mCurrentQuadIndex = 0;
539    }
540    for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
541        mActiveFonts[i]->invalidateTextureCache();
542    }
543    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
544        mCacheLines[i]->mCurrentCol = 0;
545    }
546}
547
548void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) {
549    if (cacheTexture && cacheTexture->mTexture) {
550        glDeleteTextures(1, &cacheTexture->mTextureId);
551        delete cacheTexture->mTexture;
552        cacheTexture->mTexture = NULL;
553    }
554}
555
556void FontRenderer::flushLargeCaches() {
557    if ((!mCacheTexture128 || !mCacheTexture128->mTexture) &&
558            (!mCacheTexture256 || !mCacheTexture256->mTexture) &&
559            (!mCacheTexture512 || !mCacheTexture512->mTexture)) {
560        // Typical case; no large glyph caches allocated
561        return;
562    }
563
564    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
565        CacheTextureLine* cacheLine = mCacheLines[i];
566        if ((cacheLine->mCacheTexture == mCacheTexture128 ||
567                cacheLine->mCacheTexture == mCacheTexture256 ||
568                cacheLine->mCacheTexture == mCacheTexture512) &&
569                cacheLine->mCacheTexture->mTexture != NULL) {
570            cacheLine->mCurrentCol = 0;
571            for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
572                mActiveFonts[i]->invalidateTextureCache(cacheLine);
573            }
574        }
575    }
576
577    deallocateTextureMemory(mCacheTexture128);
578    deallocateTextureMemory(mCacheTexture256);
579    deallocateTextureMemory(mCacheTexture512);
580}
581
582void FontRenderer::allocateTextureMemory(CacheTexture *cacheTexture) {
583    int width = cacheTexture->mWidth;
584    int height = cacheTexture->mHeight;
585    cacheTexture->mTexture = new uint8_t[width * height];
586    memset(cacheTexture->mTexture, 0, width * height * sizeof(uint8_t));
587    glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId);
588    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
589    // Initialize texture dimensions
590    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0,
591            GL_ALPHA, GL_UNSIGNED_BYTE, 0);
592
593    const GLenum filtering = cacheTexture->mLinearFiltering ? GL_LINEAR : GL_NEAREST;
594    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
595    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
596
597    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
598    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
599}
600
601void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
602        uint32_t* retOriginX, uint32_t* retOriginY) {
603    cachedGlyph->mIsValid = false;
604    // If the glyph is too tall, don't cache it
605    if (glyph.fHeight + TEXTURE_BORDER_SIZE > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
606        ALOGE("Font size to large to fit in cache. width, height = %i, %i",
607                (int) glyph.fWidth, (int) glyph.fHeight);
608        return;
609    }
610
611    // Now copy the bitmap into the cache texture
612    uint32_t startX = 0;
613    uint32_t startY = 0;
614
615    bool bitmapFit = false;
616    CacheTextureLine *cacheLine;
617    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
618        bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
619        if (bitmapFit) {
620            cacheLine = mCacheLines[i];
621            break;
622        }
623    }
624
625    // If the new glyph didn't fit, flush the state so far and invalidate everything
626    if (!bitmapFit) {
627        flushAllAndInvalidate();
628
629        // Try to fit it again
630        for (uint32_t i = 0; i < mCacheLines.size(); i++) {
631            bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
632            if (bitmapFit) {
633                cacheLine = mCacheLines[i];
634                break;
635            }
636        }
637
638        // if we still don't fit, something is wrong and we shouldn't draw
639        if (!bitmapFit) {
640            return;
641        }
642    }
643
644    cachedGlyph->mCachedTextureLine = cacheLine;
645
646    *retOriginX = startX;
647    *retOriginY = startY;
648
649    uint32_t endX = startX + glyph.fWidth;
650    uint32_t endY = startY + glyph.fHeight;
651
652    uint32_t cacheWidth = cacheLine->mMaxWidth;
653
654    CacheTexture *cacheTexture = cacheLine->mCacheTexture;
655    if (cacheTexture->mTexture == NULL) {
656        // Large-glyph texture memory is allocated only as needed
657        allocateTextureMemory(cacheTexture);
658    }
659    uint8_t* cacheBuffer = cacheTexture->mTexture;
660    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
661    unsigned int stride = glyph.rowBytes();
662
663    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
664    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
665        for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
666            uint8_t tempCol = bitmapBuffer[bY * stride + bX];
667            cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
668        }
669    }
670
671    cachedGlyph->mIsValid = true;
672}
673
674CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
675    GLuint textureId;
676    glGenTextures(1, &textureId);
677    uint8_t* textureMemory = NULL;
678
679    CacheTexture* cacheTexture = new CacheTexture(textureMemory, textureId, width, height);
680    if (allocate) {
681        allocateTextureMemory(cacheTexture);
682    }
683    return cacheTexture;
684}
685
686void FontRenderer::initTextTexture() {
687    mCacheLines.clear();
688
689    // Next, use other, separate caches for large glyphs.
690    uint16_t maxWidth = 0;
691    if (Caches::hasInstance()) {
692        maxWidth = Caches::getInstance().maxTextureSize;
693    }
694    if (maxWidth > MAX_TEXT_CACHE_WIDTH || maxWidth == 0) {
695        maxWidth = MAX_TEXT_CACHE_WIDTH;
696    }
697    if (mCacheTextureSmall != NULL) {
698        delete mCacheTextureSmall;
699        delete mCacheTexture128;
700        delete mCacheTexture256;
701        delete mCacheTexture512;
702    }
703    mCacheTextureSmall = createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true);
704    mCacheTexture128 = createCacheTexture(maxWidth, 256, false);
705    mCacheTexture256 = createCacheTexture(maxWidth, 256, false);
706    mCacheTexture512 = createCacheTexture(maxWidth, 512, false);
707    mCurrentCacheTexture = mCacheTextureSmall;
708
709    mUploadTexture = false;
710    // Split up our default cache texture into lines of certain widths
711    int nextLine = 0;
712    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, 0, mCacheTextureSmall));
713    nextLine += mCacheLines.top()->mMaxHeight;
714    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall));
715    nextLine += mCacheLines.top()->mMaxHeight;
716    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall));
717    nextLine += mCacheLines.top()->mMaxHeight;
718    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall));
719    nextLine += mCacheLines.top()->mMaxHeight;
720    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall));
721    nextLine += mCacheLines.top()->mMaxHeight;
722    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, 0, mCacheTextureSmall));
723    nextLine += mCacheLines.top()->mMaxHeight;
724    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine,
725            nextLine, 0, mCacheTextureSmall));
726
727    //  The first cache is split into 2 lines of height 128, the rest have just one cache line.
728    mCacheLines.push(new CacheTextureLine(maxWidth, 128, 0, 0, mCacheTexture128));
729    mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, 0, mCacheTexture128));
730    mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, 0, mCacheTexture256));
731    mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, 0, mCacheTexture512));
732}
733
734// Avoid having to reallocate memory and render quad by quad
735void FontRenderer::initVertexArrayBuffers() {
736    uint32_t numIndices = mMaxNumberOfQuads * 6;
737    uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
738    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
739
740    // Four verts, two triangles , six indices per quad
741    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
742        int i6 = i * 6;
743        int i4 = i * 4;
744
745        indexBufferData[i6 + 0] = i4 + 0;
746        indexBufferData[i6 + 1] = i4 + 1;
747        indexBufferData[i6 + 2] = i4 + 2;
748
749        indexBufferData[i6 + 3] = i4 + 0;
750        indexBufferData[i6 + 4] = i4 + 2;
751        indexBufferData[i6 + 5] = i4 + 3;
752    }
753
754    glGenBuffers(1, &mIndexBufferID);
755    Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
756    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
757
758    free(indexBufferData);
759
760    uint32_t coordSize = 2;
761    uint32_t uvSize = 2;
762    uint32_t vertsPerQuad = 4;
763    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
764    mTextMeshPtr = new float[vertexBufferSize];
765}
766
767// We don't want to allocate anything unless we actually draw text
768void FontRenderer::checkInit() {
769    if (mInitialized) {
770        return;
771    }
772
773    initTextTexture();
774    initVertexArrayBuffers();
775
776    // We store a string with letters in a rough frequency of occurrence
777    mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq ");
778    mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ");
779    mLatinPrecache += String16(",.?!()-+@;:`'");
780    mLatinPrecache += String16("0123456789");
781
782    mInitialized = true;
783}
784
785void FontRenderer::checkTextureUpdate() {
786    if (!mUploadTexture && mLastCacheTexture == mCurrentCacheTexture) {
787        return;
788    }
789
790    Caches& caches = Caches::getInstance();
791    GLuint lastTextureId = 0;
792    // Iterate over all the cache lines and see which ones need to be updated
793    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
794        CacheTextureLine* cl = mCacheLines[i];
795        if (cl->mDirty && cl->mCacheTexture->mTexture != NULL) {
796            CacheTexture* cacheTexture = cl->mCacheTexture;
797            uint32_t xOffset = 0;
798            uint32_t yOffset = cl->mCurrentRow;
799            uint32_t width   = cl->mMaxWidth;
800            uint32_t height  = cl->mMaxHeight;
801            void* textureData = cacheTexture->mTexture + (yOffset * width);
802
803            if (cacheTexture->mTextureId != lastTextureId) {
804                caches.activeTexture(0);
805                glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId);
806                lastTextureId = cacheTexture->mTextureId;
807            }
808            glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
809                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
810
811            cl->mDirty = false;
812        }
813    }
814
815    glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->mTextureId);
816    if (mLinearFiltering != mCurrentCacheTexture->mLinearFiltering) {
817        const GLenum filtering = mLinearFiltering ? GL_LINEAR : GL_NEAREST;
818        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
819        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
820        mCurrentCacheTexture->mLinearFiltering = mLinearFiltering;
821    }
822    mLastCacheTexture = mCurrentCacheTexture;
823
824    mUploadTexture = false;
825}
826
827void FontRenderer::issueDrawCommand() {
828    checkTextureUpdate();
829
830    Caches& caches = Caches::getInstance();
831    caches.bindIndicesBuffer(mIndexBufferID);
832    if (!mDrawn) {
833        float* buffer = mTextMeshPtr;
834        int offset = 2;
835
836        bool force = caches.unbindMeshBuffer();
837        caches.bindPositionVertexPointer(force, caches.currentProgram->position, buffer);
838        caches.bindTexCoordsVertexPointer(force, caches.currentProgram->texCoords,
839                buffer + offset);
840    }
841
842    glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
843
844    mDrawn = true;
845}
846
847void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
848        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
849        float x4, float y4, float u4, float v4, CacheTexture* texture) {
850    if (texture != mCurrentCacheTexture) {
851        if (mCurrentQuadIndex != 0) {
852            // First, draw everything stored already which uses the previous texture
853            issueDrawCommand();
854            mCurrentQuadIndex = 0;
855        }
856        // Now use the new texture id
857        mCurrentCacheTexture = texture;
858    }
859
860    const uint32_t vertsPerQuad = 4;
861    const uint32_t floatsPerVert = 4;
862    float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
863
864    (*currentPos++) = x1;
865    (*currentPos++) = y1;
866    (*currentPos++) = u1;
867    (*currentPos++) = v1;
868
869    (*currentPos++) = x2;
870    (*currentPos++) = y2;
871    (*currentPos++) = u2;
872    (*currentPos++) = v2;
873
874    (*currentPos++) = x3;
875    (*currentPos++) = y3;
876    (*currentPos++) = u3;
877    (*currentPos++) = v3;
878
879    (*currentPos++) = x4;
880    (*currentPos++) = y4;
881    (*currentPos++) = u4;
882    (*currentPos++) = v4;
883
884    mCurrentQuadIndex++;
885}
886
887void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
888        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
889        float x4, float y4, float u4, float v4, CacheTexture* texture) {
890
891    if (mClip &&
892            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
893        return;
894    }
895
896    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
897
898    if (mBounds) {
899        mBounds->left = fmin(mBounds->left, x1);
900        mBounds->top = fmin(mBounds->top, y3);
901        mBounds->right = fmax(mBounds->right, x3);
902        mBounds->bottom = fmax(mBounds->bottom, y1);
903    }
904
905    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
906        issueDrawCommand();
907        mCurrentQuadIndex = 0;
908    }
909}
910
911void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
912        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
913        float x4, float y4, float u4, float v4, CacheTexture* texture) {
914
915    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
916
917    if (mBounds) {
918        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
919        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
920        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
921        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
922    }
923
924    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
925        issueDrawCommand();
926        mCurrentQuadIndex = 0;
927    }
928}
929
930uint32_t FontRenderer::getRemainingCacheCapacity() {
931    uint32_t remainingCapacity = 0;
932    float totalPixels = 0;
933    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
934         remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol);
935         totalPixels += mCacheLines[i]->mMaxWidth;
936    }
937    remainingCapacity = (remainingCapacity * 100) / totalPixels;
938    return remainingCapacity;
939}
940
941void FontRenderer::precacheLatin(SkPaint* paint) {
942    // Remaining capacity is measured in %
943    uint32_t remainingCapacity = getRemainingCacheCapacity();
944    uint32_t precacheIdx = 0;
945    while (remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) {
946        mCurrentFont->getCachedGlyph(paint, (int32_t) mLatinPrecache[precacheIdx]);
947        remainingCapacity = getRemainingCacheCapacity();
948        precacheIdx ++;
949    }
950}
951
952void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
953    uint32_t currentNumFonts = mActiveFonts.size();
954    int flags = 0;
955    if (paint->isFakeBoldText()) {
956        flags |= Font::kFakeBold;
957    }
958
959    const float skewX = paint->getTextSkewX();
960    uint32_t italicStyle = *(uint32_t*) &skewX;
961    const float scaleXFloat = paint->getTextScaleX();
962    uint32_t scaleX = *(uint32_t*) &scaleXFloat;
963    SkPaint::Style style = paint->getStyle();
964    const float strokeWidthFloat = paint->getStrokeWidth();
965    uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
966    mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle,
967            scaleX, style, strokeWidth);
968
969    const float maxPrecacheFontSize = 40.0f;
970    bool isNewFont = currentNumFonts != mActiveFonts.size();
971
972    if (isNewFont && fontSize <= maxPrecacheFontSize) {
973        precacheLatin(paint);
974    }
975}
976
977FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
978        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
979    checkInit();
980
981    if (!mCurrentFont) {
982        DropShadow image;
983        image.width = 0;
984        image.height = 0;
985        image.image = NULL;
986        image.penX = 0;
987        image.penY = 0;
988        return image;
989    }
990
991    mDrawn = false;
992    mClip = NULL;
993    mBounds = NULL;
994
995    Rect bounds;
996    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds);
997
998    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
999    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
1000    uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
1001
1002    for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
1003        dataBuffer[i] = 0;
1004    }
1005
1006    int penX = radius - bounds.left;
1007    int penY = radius - bounds.bottom;
1008
1009    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
1010            dataBuffer, paddedWidth, paddedHeight);
1011    blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
1012
1013    DropShadow image;
1014    image.width = paddedWidth;
1015    image.height = paddedHeight;
1016    image.image = dataBuffer;
1017    image.penX = penX;
1018    image.penY = penY;
1019
1020    return image;
1021}
1022
1023void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
1024    checkInit();
1025
1026    mDrawn = false;
1027    mBounds = bounds;
1028    mClip = clip;
1029}
1030
1031void FontRenderer::finishRender() {
1032    mBounds = NULL;
1033    mClip = NULL;
1034
1035    if (mCurrentQuadIndex != 0) {
1036        issueDrawCommand();
1037        mCurrentQuadIndex = 0;
1038    }
1039}
1040
1041bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
1042        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) {
1043    if (!mCurrentFont) {
1044        ALOGE("No font set");
1045        return false;
1046    }
1047
1048    initRender(clip, bounds);
1049    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y);
1050    finishRender();
1051
1052    return mDrawn;
1053}
1054
1055bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
1056        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
1057        const float* positions, Rect* bounds) {
1058    if (!mCurrentFont) {
1059        ALOGE("No font set");
1060        return false;
1061    }
1062
1063    initRender(clip, bounds);
1064    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
1065    finishRender();
1066
1067    return mDrawn;
1068}
1069
1070bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
1071        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
1072        float hOffset, float vOffset, Rect* bounds) {
1073    if (!mCurrentFont) {
1074        ALOGE("No font set");
1075        return false;
1076    }
1077
1078    initRender(clip, bounds);
1079    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
1080    finishRender();
1081
1082    return mDrawn;
1083}
1084
1085void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
1086    // Compute gaussian weights for the blur
1087    // e is the euler's number
1088    float e = 2.718281828459045f;
1089    float pi = 3.1415926535897932f;
1090    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
1091    // x is of the form [-radius .. 0 .. radius]
1092    // and sigma varies with radius.
1093    // Based on some experimental radius values and sigma's
1094    // we approximately fit sigma = f(radius) as
1095    // sigma = radius * 0.3  + 0.6
1096    // The larger the radius gets, the more our gaussian blur
1097    // will resemble a box blur since with large sigma
1098    // the gaussian curve begins to lose its shape
1099    float sigma = 0.3f * (float) radius + 0.6f;
1100
1101    // Now compute the coefficints
1102    // We will store some redundant values to save some math during
1103    // the blur calculations
1104    // precompute some values
1105    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
1106    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
1107
1108    float normalizeFactor = 0.0f;
1109    for (int32_t r = -radius; r <= radius; r ++) {
1110        float floatR = (float) r;
1111        weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
1112        normalizeFactor += weights[r + radius];
1113    }
1114
1115    //Now we need to normalize the weights because all our coefficients need to add up to one
1116    normalizeFactor = 1.0f / normalizeFactor;
1117    for (int32_t r = -radius; r <= radius; r ++) {
1118        weights[r + radius] *= normalizeFactor;
1119    }
1120}
1121
1122void FontRenderer::horizontalBlur(float* weights, int32_t radius,
1123        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
1124    float blurredPixel = 0.0f;
1125    float currentPixel = 0.0f;
1126
1127    for (int32_t y = 0; y < height; y ++) {
1128
1129        const uint8_t* input = source + y * width;
1130        uint8_t* output = dest + y * width;
1131
1132        for (int32_t x = 0; x < width; x ++) {
1133            blurredPixel = 0.0f;
1134            const float* gPtr = weights;
1135            // Optimization for non-border pixels
1136            if (x > radius && x < (width - radius)) {
1137                const uint8_t *i = input + (x - radius);
1138                for (int r = -radius; r <= radius; r ++) {
1139                    currentPixel = (float) (*i);
1140                    blurredPixel += currentPixel * gPtr[0];
1141                    gPtr++;
1142                    i++;
1143                }
1144            } else {
1145                for (int32_t r = -radius; r <= radius; r ++) {
1146                    // Stepping left and right away from the pixel
1147                    int validW = x + r;
1148                    if (validW < 0) {
1149                        validW = 0;
1150                    }
1151                    if (validW > width - 1) {
1152                        validW = width - 1;
1153                    }
1154
1155                    currentPixel = (float) input[validW];
1156                    blurredPixel += currentPixel * gPtr[0];
1157                    gPtr++;
1158                }
1159            }
1160            *output = (uint8_t)blurredPixel;
1161            output ++;
1162        }
1163    }
1164}
1165
1166void FontRenderer::verticalBlur(float* weights, int32_t radius,
1167        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
1168    float blurredPixel = 0.0f;
1169    float currentPixel = 0.0f;
1170
1171    for (int32_t y = 0; y < height; y ++) {
1172
1173        uint8_t* output = dest + y * width;
1174
1175        for (int32_t x = 0; x < width; x ++) {
1176            blurredPixel = 0.0f;
1177            const float* gPtr = weights;
1178            const uint8_t* input = source + x;
1179            // Optimization for non-border pixels
1180            if (y > radius && y < (height - radius)) {
1181                const uint8_t *i = input + ((y - radius) * width);
1182                for (int32_t r = -radius; r <= radius; r ++) {
1183                    currentPixel = (float)(*i);
1184                    blurredPixel += currentPixel * gPtr[0];
1185                    gPtr++;
1186                    i += width;
1187                }
1188            } else {
1189                for (int32_t r = -radius; r <= radius; r ++) {
1190                    int validH = y + r;
1191                    // Clamp to zero and width
1192                    if (validH < 0) {
1193                        validH = 0;
1194                    }
1195                    if (validH > height - 1) {
1196                        validH = height - 1;
1197                    }
1198
1199                    const uint8_t *i = input + validH * width;
1200                    currentPixel = (float) (*i);
1201                    blurredPixel += currentPixel * gPtr[0];
1202                    gPtr++;
1203                }
1204            }
1205            *output = (uint8_t) blurredPixel;
1206            output ++;
1207        }
1208    }
1209}
1210
1211
1212void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
1213    float *gaussian = new float[2 * radius + 1];
1214    computeGaussianWeights(gaussian, radius);
1215
1216    uint8_t* scratch = new uint8_t[width * height];
1217
1218    horizontalBlur(gaussian, radius, image, scratch, width, height);
1219    verticalBlur(gaussian, radius, scratch, image, width, height);
1220
1221    delete[] gaussian;
1222    delete[] scratch;
1223}
1224
1225}; // namespace uirenderer
1226}; // namespace android
1227