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