FontRenderer.cpp revision 89a524ac2d4a36739e51b01b336c0bade77e2ee0
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#include <utils/Log.h>
23
24#include "FontRenderer.h"
25
26namespace android {
27namespace uirenderer {
28
29///////////////////////////////////////////////////////////////////////////////
30// Defines
31///////////////////////////////////////////////////////////////////////////////
32
33#define DEFAULT_TEXT_CACHE_WIDTH 1024
34#define DEFAULT_TEXT_CACHE_HEIGHT 256
35
36///////////////////////////////////////////////////////////////////////////////
37// Font
38///////////////////////////////////////////////////////////////////////////////
39
40Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) :
41    mState(state), mFontId(fontId), mFontSize(fontSize) {
42}
43
44
45Font::~Font() {
46    for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
47        if (mState->mActiveFonts[ct] == this) {
48            mState->mActiveFonts.removeAt(ct);
49            break;
50        }
51    }
52
53    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
54        CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i);
55        delete glyph;
56    }
57}
58
59void Font::invalidateTextureCache() {
60    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
61        mCachedGlyphs.valueAt(i)->mIsValid = false;
62    }
63}
64
65void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
66    int nPenX = x + glyph->mBitmapLeft;
67    int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
68
69    float u1 = glyph->mBitmapMinU;
70    float u2 = glyph->mBitmapMaxU;
71    float v1 = glyph->mBitmapMinV;
72    float v2 = glyph->mBitmapMaxV;
73
74    int width = (int) glyph->mBitmapWidth;
75    int height = (int) glyph->mBitmapHeight;
76
77    mState->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
78            nPenX + width, nPenY, 0, u2, v2,
79            nPenX + width, nPenY - height, 0, u2, v1,
80            nPenX, nPenY - height, 0, u1, v1);
81}
82
83void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
84                             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
85    int nPenX = x + glyph->mBitmapLeft;
86    int nPenY = y + glyph->mBitmapTop;
87
88    uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
89    uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
90
91    if(nPenX < 0 || nPenY < 0) {
92        LOGE("Cannot render into a bitmap, some of the glyph is below zero");
93        return;
94    }
95
96    if(nPenX + glyph->mBitmapWidth >= bitmapW || nPenY + glyph->mBitmapHeight >= bitmapH) {
97        LOGE("Cannot render into a bitmap, dimentions too small");
98        return;
99    }
100
101    uint32_t cacheWidth = mState->getCacheWidth();
102    const uint8_t* cacheBuffer = mState->getTextTextureData();
103
104    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
105    for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
106        for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
107            uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
108            bitmap[bY * bitmapW + bX] = tempCol;
109        }
110    }
111
112}
113
114Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) {
115    CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(utfChar);
116    if (cachedGlyph == NULL) {
117        cachedGlyph = cacheGlyph(paint, utfChar);
118    }
119
120    // Is the glyph still in texture cache?
121    if (!cachedGlyph->mIsValid) {
122        const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
123        updateGlyphCache(paint, skiaGlyph, cachedGlyph);
124    }
125
126    return cachedGlyph;
127}
128
129void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
130        int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
131    if (numGlyphs == 0 || text == NULL || len == 0) {
132        return;
133    }
134
135    int penX = x, penY = y;
136    int glyphsLeft = 1;
137    if (numGlyphs > 0) {
138        glyphsLeft = numGlyphs;
139    }
140
141    text += start;
142
143    while (glyphsLeft > 0) {
144        int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
145
146        // Reached the end of the string or encountered
147        if (utfChar < 0) {
148            break;
149        }
150
151        CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar);
152
153        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
154        if (cachedGlyph->mIsValid) {
155            if(bitmap != NULL) {
156                drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
157            }
158            else {
159                drawCachedGlyph(cachedGlyph, penX, penY);
160            }
161        }
162
163        penX += SkFixedFloor(cachedGlyph->mAdvanceX);
164
165        // If we were given a specific number of glyphs, decrement
166        if (numGlyphs > 0) {
167            glyphsLeft--;
168        }
169    }
170}
171
172void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
173    glyph->mAdvanceX = skiaGlyph.fAdvanceX;
174    glyph->mAdvanceY = skiaGlyph.fAdvanceY;
175    glyph->mBitmapLeft = skiaGlyph.fLeft;
176    glyph->mBitmapTop = skiaGlyph.fTop;
177
178    uint32_t startX = 0;
179    uint32_t startY = 0;
180
181    // Get the bitmap for the glyph
182    paint->findImage(skiaGlyph);
183    glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY);
184
185    if (!glyph->mIsValid) {
186        return;
187    }
188
189    uint32_t endX = startX + skiaGlyph.fWidth;
190    uint32_t endY = startY + skiaGlyph.fHeight;
191
192    glyph->mStartX = startX;
193    glyph->mStartY = startY;
194    glyph->mBitmapWidth = skiaGlyph.fWidth;
195    glyph->mBitmapHeight = skiaGlyph.fHeight;
196
197    uint32_t cacheWidth = mState->getCacheWidth();
198    uint32_t cacheHeight = mState->getCacheHeight();
199
200    glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
201    glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
202    glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
203    glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
204
205    mState->mUploadTexture = true;
206}
207
208Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
209    CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
210    mCachedGlyphs.add(glyph, newGlyph);
211
212    const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
213    newGlyph->mGlyphIndex = skiaGlyph.fID;
214    newGlyph->mIsValid = false;
215
216    updateGlyphCache(paint, skiaGlyph, newGlyph);
217
218    return newGlyph;
219}
220
221Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) {
222    Vector<Font*> &activeFonts = state->mActiveFonts;
223
224    for (uint32_t i = 0; i < activeFonts.size(); i++) {
225        Font* font = activeFonts[i];
226        if (font->mFontId == fontId && font->mFontSize == fontSize) {
227            return font;
228        }
229    }
230
231    Font* newFont = new Font(state, fontId, fontSize);
232    activeFonts.push(newFont);
233    return newFont;
234}
235
236///////////////////////////////////////////////////////////////////////////////
237// FontRenderer
238///////////////////////////////////////////////////////////////////////////////
239
240FontRenderer::FontRenderer() {
241    LOGD("Creating FontRenderer");
242
243    mInitialized = false;
244    mMaxNumberOfQuads = 1024;
245    mCurrentQuadIndex = 0;
246    mTextureId = 0;
247
248    mIndexBufferID = 0;
249
250    mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
251    mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
252
253    char property[PROPERTY_VALUE_MAX];
254    if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
255        LOGD("  Setting text cache width to %s pixels", property);
256        mCacheWidth = atoi(property);
257    } else {
258        LOGD("  Using default text cache width of %i pixels", mCacheWidth);
259    }
260
261    if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) {
262        LOGD("  Setting text cache width to %s pixels", property);
263        mCacheHeight = atoi(property);
264    } else {
265        LOGD("  Using default text cache height of %i pixels", mCacheHeight);
266    }
267}
268
269FontRenderer::~FontRenderer() {
270    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
271        delete mCacheLines[i];
272    }
273    mCacheLines.clear();
274
275    delete[] mTextMeshPtr;
276    delete[] mTextTexture;
277
278    if(mTextureId) {
279        glDeleteTextures(1, &mTextureId);
280    }
281
282    Vector<Font*> fontsToDereference = mActiveFonts;
283    for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
284        delete fontsToDereference[i];
285    }
286}
287
288void FontRenderer::flushAllAndInvalidate() {
289    if (mCurrentQuadIndex != 0) {
290        issueDrawCommand();
291        mCurrentQuadIndex = 0;
292    }
293    for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
294        mActiveFonts[i]->invalidateTextureCache();
295    }
296    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
297        mCacheLines[i]->mCurrentCol = 0;
298    }
299}
300
301bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
302    // If the glyph is too tall, don't cache it
303    if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
304        LOGE("Font size to large to fit in cache. width, height = %i, %i",
305                (int) glyph.fWidth, (int) glyph.fHeight);
306        return false;
307    }
308
309    // Now copy the bitmap into the cache texture
310    uint32_t startX = 0;
311    uint32_t startY = 0;
312
313    bool bitmapFit = false;
314    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
315        bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
316        if (bitmapFit) {
317            break;
318        }
319    }
320
321    // If the new glyph didn't fit, flush the state so far and invalidate everything
322    if (!bitmapFit) {
323        flushAllAndInvalidate();
324
325        // Try to fit it again
326        for (uint32_t i = 0; i < mCacheLines.size(); i++) {
327            bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
328            if (bitmapFit) {
329                break;
330            }
331        }
332
333        // if we still don't fit, something is wrong and we shouldn't draw
334        if (!bitmapFit) {
335            LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
336                    (int) glyph.fWidth, (int) glyph.fHeight);
337            return false;
338        }
339    }
340
341    *retOriginX = startX;
342    *retOriginY = startY;
343
344    uint32_t endX = startX + glyph.fWidth;
345    uint32_t endY = startY + glyph.fHeight;
346
347    uint32_t cacheWidth = mCacheWidth;
348
349    uint8_t* cacheBuffer = mTextTexture;
350    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
351    unsigned int stride = glyph.rowBytes();
352
353    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
354    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
355        for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
356            uint8_t tempCol = bitmapBuffer[bY * stride + bX];
357            cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
358        }
359    }
360
361    return true;
362}
363
364void FontRenderer::initTextTexture() {
365    mTextTexture = new uint8_t[mCacheWidth * mCacheHeight];
366    mUploadTexture = false;
367
368    glGenTextures(1, &mTextureId);
369    glBindTexture(GL_TEXTURE_2D, mTextureId);
370    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
371    // Initialize texture dimentions
372    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
373                  GL_ALPHA, GL_UNSIGNED_BYTE, 0);
374
375    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
376    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
377
378    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
379    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
380
381    // Split up our cache texture into lines of certain widths
382    int nextLine = 0;
383    mCacheLines.push(new CacheTextureLine(mCacheWidth, 16, nextLine, 0));
384    nextLine += mCacheLines.top()->mMaxHeight;
385    mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
386    nextLine += mCacheLines.top()->mMaxHeight;
387    mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
388    nextLine += mCacheLines.top()->mMaxHeight;
389    mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
390    nextLine += mCacheLines.top()->mMaxHeight;
391    mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
392    nextLine += mCacheLines.top()->mMaxHeight;
393    mCacheLines.push(new CacheTextureLine(mCacheWidth, 40, nextLine, 0));
394    nextLine += mCacheLines.top()->mMaxHeight;
395    mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
396}
397
398// Avoid having to reallocate memory and render quad by quad
399void FontRenderer::initVertexArrayBuffers() {
400    uint32_t numIndicies = mMaxNumberOfQuads * 6;
401    uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
402    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
403
404    // Four verts, two triangles , six indices per quad
405    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
406        int i6 = i * 6;
407        int i4 = i * 4;
408
409        indexBufferData[i6 + 0] = i4 + 0;
410        indexBufferData[i6 + 1] = i4 + 1;
411        indexBufferData[i6 + 2] = i4 + 2;
412
413        indexBufferData[i6 + 3] = i4 + 0;
414        indexBufferData[i6 + 4] = i4 + 2;
415        indexBufferData[i6 + 5] = i4 + 3;
416    }
417
418    glGenBuffers(1, &mIndexBufferID);
419    glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID);
420    glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW);
421    glBindBuffer(GL_ARRAY_BUFFER, 0);
422
423    free(indexBufferData);
424
425    uint32_t coordSize = 3;
426    uint32_t uvSize = 2;
427    uint32_t vertsPerQuad = 4;
428    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
429    mTextMeshPtr = new float[vertexBufferSize];
430}
431
432// We don't want to allocate anything unless we actually draw text
433void FontRenderer::checkInit() {
434    if (mInitialized) {
435        return;
436    }
437
438    initTextTexture();
439    initVertexArrayBuffers();
440
441    // We store a string with letters in a rough frequency of occurrence
442    mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq ");
443    mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ");
444    mLatinPrecache += String16(",.?!()-+@;:`'");
445    mLatinPrecache += String16("0123456789");
446
447    mInitialized = true;
448}
449
450void FontRenderer::checkTextureUpdate() {
451    if (!mUploadTexture) {
452        return;
453    }
454
455    glBindTexture(GL_TEXTURE_2D, mTextureId);
456
457    // Iterate over all the cache lines and see which ones need to be updated
458    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
459        CacheTextureLine* cl = mCacheLines[i];
460        if(cl->mDirty) {
461            uint32_t xOffset = 0;
462            uint32_t yOffset = cl->mCurrentRow;
463            uint32_t width   = mCacheWidth;
464            uint32_t height  = cl->mMaxHeight;
465            void*    textureData = mTextTexture + yOffset*width;
466
467            glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
468                             GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
469
470            cl->mDirty = false;
471        }
472    }
473
474    mUploadTexture = false;
475}
476
477void FontRenderer::issueDrawCommand() {
478
479    checkTextureUpdate();
480
481    float* vtx = mTextMeshPtr;
482    float* tex = vtx + 3;
483
484    // position is slot 0
485    uint32_t slot = 0;
486    glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
487
488    // texture0 is slot 1
489    slot = 1;
490    glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
491
492    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
493    glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
494}
495
496void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
497        float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
498        float x4, float y4, float z4, float u4, float v4) {
499    if (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom) {
500        return;
501    }
502
503    const uint32_t vertsPerQuad = 4;
504    const uint32_t floatsPerVert = 5;
505    float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
506
507    (*currentPos++) = x1;
508    (*currentPos++) = y1;
509    (*currentPos++) = z1;
510    (*currentPos++) = u1;
511    (*currentPos++) = v1;
512
513    (*currentPos++) = x2;
514    (*currentPos++) = y2;
515    (*currentPos++) = z2;
516    (*currentPos++) = u2;
517    (*currentPos++) = v2;
518
519    (*currentPos++) = x3;
520    (*currentPos++) = y3;
521    (*currentPos++) = z3;
522    (*currentPos++) = u3;
523    (*currentPos++) = v3;
524
525    (*currentPos++) = x4;
526    (*currentPos++) = y4;
527    (*currentPos++) = z4;
528    (*currentPos++) = u4;
529    (*currentPos++) = v4;
530
531    mCurrentQuadIndex++;
532
533    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
534        issueDrawCommand();
535        mCurrentQuadIndex = 0;
536    }
537}
538
539uint32_t FontRenderer::getRemainingCacheCapacity() {
540    uint32_t remainingCapacity = 0;
541    float totalPixels = 0;
542    for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
543         remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol);
544         totalPixels += mCacheLines[i]->mMaxWidth;
545    }
546    remainingCapacity = (remainingCapacity * 100) / totalPixels;
547    return remainingCapacity;
548}
549
550void FontRenderer::precacheLatin(SkPaint* paint) {
551    // Remaining capacity is measured in %
552    uint32_t remainingCapacity = getRemainingCacheCapacity();
553    uint32_t precacheIdx = 0;
554    while(remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) {
555        mCurrentFont->getCachedUTFChar(paint, (int32_t)mLatinPrecache[precacheIdx]);
556        remainingCapacity = getRemainingCacheCapacity();
557        precacheIdx ++;
558    }
559}
560
561void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
562    uint32_t currentNumFonts = mActiveFonts.size();
563    mCurrentFont = Font::create(this, fontId, fontSize);
564
565    const float maxPrecacheFontSize = 40.0f;
566    bool isNewFont = currentNumFonts != mActiveFonts.size();
567
568    if(isNewFont && fontSize <= maxPrecacheFontSize ){
569        precacheLatin(paint);
570    }
571}
572
573void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
574        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
575    checkInit();
576
577    if (!mCurrentFont) {
578        LOGE("No font set");
579        return;
580    }
581
582    mClip = clip;
583    mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y);
584
585    if (mCurrentQuadIndex != 0) {
586        issueDrawCommand();
587        mCurrentQuadIndex = 0;
588    }
589}
590
591void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
592    // Compute gaussian weights for the blur
593    // e is the euler's number
594    float e = 2.718281828459045f;
595    float pi = 3.1415926535897932f;
596    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
597    // x is of the form [-radius .. 0 .. radius]
598    // and sigma varies with radius.
599    // Based on some experimental radius values and sigma's
600    // we approximately fit sigma = f(radius) as
601    // sigma = radius * 0.4  + 0.6
602    // The larger the radius gets, the more our gaussian blur
603    // will resemble a box blur since with large sigma
604    // the gaussian curve begins to lose its shape
605    float sigma = 0.4f * (float)radius + 0.6f;
606
607    // Now compute the coefficints
608    // We will store some redundant values to save some math during
609    // the blur calculations
610    // precompute some values
611    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
612    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
613
614    float normalizeFactor = 0.0f;
615    for(int32_t r = -radius; r <= radius; r ++) {
616        float floatR = (float)r;
617        weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
618        normalizeFactor += weights[r + radius];
619    }
620
621    //Now we need to normalize the weights because all our coefficients need to add up to one
622    normalizeFactor = 1.0f / normalizeFactor;
623    for(int32_t r = -radius; r <= radius; r ++) {
624        weights[r + radius] *= normalizeFactor;
625    }
626}
627
628void FontRenderer::horizontalBlur(float* weights, int32_t radius,
629                                    const uint8_t* source, uint8_t* dest,
630                                    int32_t width, int32_t height) {
631    float blurredPixel = 0.0f;
632    float currentPixel = 0.0f;
633
634    for(int32_t y = 0; y < height; y ++) {
635
636        const uint8_t* input = source + y * width;
637        uint8_t* output = dest + y * width;
638
639        for(int32_t x = 0; x < width; x ++) {
640            blurredPixel = 0.0f;
641            const float* gPtr = weights;
642            // Optimization for non-border pixels
643            if ((x > radius) && (x < (width - radius))) {
644                const uint8_t *i = input + (x - radius);
645                for(int r = -radius; r <= radius; r ++) {
646                    currentPixel = (float)(*i);
647                    blurredPixel += currentPixel * gPtr[0];
648                    gPtr++;
649                    i++;
650                }
651            } else {
652                for(int32_t r = -radius; r <= radius; r ++) {
653                    // Stepping left and right away from the pixel
654                    int validW = x + r;
655                    if(validW < 0) {
656                        validW = 0;
657                    }
658                    if(validW > width - 1) {
659                        validW = width - 1;
660                    }
661
662                    currentPixel = (float)(input[validW]);
663                    blurredPixel += currentPixel * gPtr[0];
664                    gPtr++;
665                }
666            }
667            *output = (uint8_t)blurredPixel;
668            output ++;
669        }
670    }
671}
672
673void FontRenderer::verticalBlur(float* weights, int32_t radius,
674                                  const uint8_t* source, uint8_t* dest,
675                                  int32_t width, int32_t height) {
676    float blurredPixel = 0.0f;
677    float currentPixel = 0.0f;
678
679    for(int32_t y = 0; y < height; y ++) {
680
681        uint8_t* output = dest + y * width;
682
683        for(int32_t x = 0; x < width; x ++) {
684            blurredPixel = 0.0f;
685            const float* gPtr = weights;
686            const uint8_t* input = source + x;
687            // Optimization for non-border pixels
688            if ((y > radius) && (y < (height - radius))) {
689                const uint8_t *i = input + ((y - radius) * width);
690                for(int32_t r = -radius; r <= radius; r ++) {
691                    currentPixel = (float)(*i);
692                    blurredPixel += currentPixel * gPtr[0];
693                    gPtr++;
694                    i += width;
695                }
696            } else {
697                for(int32_t r = -radius; r <= radius; r ++) {
698                    int validH = y + r;
699                    // Clamp to zero and width
700                    if(validH < 0) {
701                        validH = 0;
702                    }
703                    if(validH > height - 1) {
704                        validH = height - 1;
705                    }
706
707                    const uint8_t *i = input + validH * width;
708                    currentPixel = (float)(*i);
709                    blurredPixel += currentPixel * gPtr[0];
710                    gPtr++;
711                }
712            }
713            *output = (uint8_t)blurredPixel;
714            output ++;
715        }
716    }
717}
718
719
720void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
721    float *gaussian = new float[2 * radius + 1];
722    computeGaussianWeights(gaussian, radius);
723    uint8_t* scratch = new uint8_t[width * height];
724    horizontalBlur(gaussian, radius, image, scratch, width, height);
725    verticalBlur(gaussian, radius, scratch, image, width, height);
726    delete[] gaussian;
727    delete[] scratch;
728}
729
730}; // namespace uirenderer
731}; // namespace android
732