FontRenderer.cpp revision 19d4f1806a953b87435a2bb6fd8e738424e94003
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "OpenGLRenderer"
18
19#include <SkGlyph.h>
20#include <SkUtils.h>
21
22#include <cutils/properties.h>
23
24#include <utils/Log.h>
25
26#include "Caches.h"
27#include "Debug.h"
28#include "FontRenderer.h"
29#include "Rect.h"
30
31namespace android {
32namespace uirenderer {
33
34///////////////////////////////////////////////////////////////////////////////
35// FontRenderer
36///////////////////////////////////////////////////////////////////////////////
37
38static bool sLogFontRendererCreate = true;
39
40FontRenderer::FontRenderer() :
41        mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) {
42
43    if (sLogFontRendererCreate) {
44        INIT_LOGD("Creating FontRenderer");
45    }
46
47    mGammaTable = NULL;
48    mInitialized = false;
49    mMaxNumberOfQuads = 1024;
50    mCurrentQuadIndex = 0;
51    mLastQuadIndex = 0;
52
53    mTextMesh = NULL;
54    mCurrentCacheTexture = NULL;
55
56    mLinearFiltering = false;
57
58    mIndexBufferID = 0;
59
60    mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
61    mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
62    mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
63    mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
64
65    char property[PROPERTY_VALUE_MAX];
66    if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) {
67        mSmallCacheWidth = atoi(property);
68    }
69
70    if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) {
71        mSmallCacheHeight = atoi(property);
72    }
73
74    if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) {
75        mLargeCacheWidth = atoi(property);
76    }
77
78    if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) {
79        mLargeCacheHeight = atoi(property);
80    }
81
82    uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
83    mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth;
84    mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight;
85    mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth;
86    mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight;
87
88    if (sLogFontRendererCreate) {
89        INIT_LOGD("  Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
90                mSmallCacheWidth, mSmallCacheHeight,
91                mLargeCacheWidth, mLargeCacheHeight >> 1,
92                mLargeCacheWidth, mLargeCacheHeight >> 1,
93                mLargeCacheWidth, mLargeCacheHeight);
94    }
95
96    sLogFontRendererCreate = false;
97}
98
99FontRenderer::~FontRenderer() {
100    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
101        delete mCacheTextures[i];
102    }
103    mCacheTextures.clear();
104
105    if (mInitialized) {
106        // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
107        Caches::getInstance().unbindIndicesBuffer();
108        glDeleteBuffers(1, &mIndexBufferID);
109
110        delete[] mTextMesh;
111    }
112
113    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
114    while (it.next()) {
115        delete it.value();
116    }
117    mActiveFonts.clear();
118}
119
120void FontRenderer::flushAllAndInvalidate() {
121    if (mCurrentQuadIndex != 0) {
122        issueDrawCommand();
123    }
124
125    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
126    while (it.next()) {
127        it.value()->invalidateTextureCache();
128    }
129
130    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
131        mCacheTextures[i]->init();
132    }
133
134#if DEBUG_FONT_RENDERER
135    uint16_t totalGlyphs = 0;
136    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
137        totalGlyphs += mCacheTextures[i]->getGlyphCount();
138        // Erase caches, just as a debugging facility
139        if (mCacheTextures[i]->getTexture()) {
140            memset(mCacheTextures[i]->getTexture(), 0,
141                    mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight());
142        }
143    }
144    ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs);
145#endif
146}
147
148void FontRenderer::flushLargeCaches() {
149    // Start from 1; don't deallocate smallest/default texture
150    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
151        CacheTexture* cacheTexture = mCacheTextures[i];
152        if (cacheTexture->getTexture()) {
153            cacheTexture->init();
154            LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
155            while (it.next()) {
156                it.value()->invalidateTextureCache(cacheTexture);
157            }
158            cacheTexture->releaseTexture();
159        }
160    }
161}
162
163CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
164        uint32_t* startX, uint32_t* startY) {
165    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
166        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
167            return mCacheTextures[i];
168        }
169    }
170    // Could not fit glyph into current cache textures
171    return NULL;
172}
173
174void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
175        uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
176    checkInit();
177    cachedGlyph->mIsValid = false;
178    // If the glyph is too tall, don't cache it
179    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
180                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
181        ALOGE("Font size too large to fit in cache. width, height = %i, %i",
182                (int) glyph.fWidth, (int) glyph.fHeight);
183        return;
184    }
185
186    // Now copy the bitmap into the cache texture
187    uint32_t startX = 0;
188    uint32_t startY = 0;
189
190    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
191
192    if (!cacheTexture) {
193        if (!precaching) {
194            // If the new glyph didn't fit and we are not just trying to precache it,
195            // clear out the cache and try again
196            flushAllAndInvalidate();
197            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
198        }
199
200        if (!cacheTexture) {
201            // either the glyph didn't fit or we're precaching and will cache it when we draw
202            return;
203        }
204    }
205
206    cachedGlyph->mCacheTexture = cacheTexture;
207
208    *retOriginX = startX;
209    *retOriginY = startY;
210
211    uint32_t endX = startX + glyph.fWidth;
212    uint32_t endY = startY + glyph.fHeight;
213
214    uint32_t cacheWidth = cacheTexture->getWidth();
215
216    if (!cacheTexture->getTexture()) {
217        Caches::getInstance().activeTexture(0);
218        // Large-glyph texture memory is allocated only as needed
219        cacheTexture->allocateTexture();
220    }
221
222    uint8_t* cacheBuffer = cacheTexture->getTexture();
223    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
224    unsigned int stride = glyph.rowBytes();
225
226    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
227
228    for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) {
229        cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0;
230        cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0;
231    }
232
233    for (cacheY = startY - TEXTURE_BORDER_SIZE + 1;
234            cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) {
235        cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0;
236        cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0;
237    }
238
239    if (mGammaTable) {
240        for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
241            for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
242                uint8_t tempCol = bitmapBuffer[bY * stride + bX];
243                cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
244            }
245        }
246    } else {
247        for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
248            for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
249                uint8_t tempCol = bitmapBuffer[bY * stride + bX];
250                cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
251            }
252        }
253    }
254
255    cachedGlyph->mIsValid = true;
256}
257
258CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
259    CacheTexture* cacheTexture = new CacheTexture(width, height);
260
261    if (allocate) {
262        Caches::getInstance().activeTexture(0);
263        cacheTexture->allocateTexture();
264    }
265
266    return cacheTexture;
267}
268
269void FontRenderer::initTextTexture() {
270    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
271        delete mCacheTextures[i];
272    }
273    mCacheTextures.clear();
274
275    mUploadTexture = false;
276    mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
277    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
278    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
279    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
280    mCurrentCacheTexture = mCacheTextures[0];
281}
282
283// Avoid having to reallocate memory and render quad by quad
284void FontRenderer::initVertexArrayBuffers() {
285    uint32_t numIndices = mMaxNumberOfQuads * 6;
286    uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
287    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
288
289    // Four verts, two triangles , six indices per quad
290    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
291        int i6 = i * 6;
292        int i4 = i * 4;
293
294        indexBufferData[i6 + 0] = i4 + 0;
295        indexBufferData[i6 + 1] = i4 + 1;
296        indexBufferData[i6 + 2] = i4 + 2;
297
298        indexBufferData[i6 + 3] = i4 + 0;
299        indexBufferData[i6 + 4] = i4 + 2;
300        indexBufferData[i6 + 5] = i4 + 3;
301    }
302
303    glGenBuffers(1, &mIndexBufferID);
304    Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
305    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
306
307    free(indexBufferData);
308
309    uint32_t coordSize = 2;
310    uint32_t uvSize = 2;
311    uint32_t vertsPerQuad = 4;
312    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
313    mTextMesh = new float[vertexBufferSize];
314}
315
316// We don't want to allocate anything unless we actually draw text
317void FontRenderer::checkInit() {
318    if (mInitialized) {
319        return;
320    }
321
322    initTextTexture();
323    initVertexArrayBuffers();
324
325    mInitialized = true;
326}
327
328void FontRenderer::updateDrawParams() {
329    if (mCurrentQuadIndex != mLastQuadIndex) {
330        mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6));
331        mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex);
332        mDrawCacheTextures.add(mCurrentCacheTexture);
333        mLastQuadIndex = mCurrentQuadIndex;
334    }
335}
336
337void FontRenderer::checkTextureUpdate() {
338    if (!mUploadTexture) {
339        return;
340    }
341
342    Caches& caches = Caches::getInstance();
343    GLuint lastTextureId = 0;
344    // Iterate over all the cache textures and see which ones need to be updated
345    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
346        CacheTexture* cacheTexture = mCacheTextures[i];
347        if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
348            // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
349            // of data. So expand the dirty rect to the encompassing horizontal stripe.
350            const Rect* dirtyRect = cacheTexture->getDirtyRect();
351            uint32_t x = 0;
352            uint32_t y = dirtyRect->top;
353            uint32_t width = cacheTexture->getWidth();
354            uint32_t height = dirtyRect->getHeight();
355            void* textureData = cacheTexture->getTexture() + y * width;
356
357            if (cacheTexture->getTextureId() != lastTextureId) {
358                lastTextureId = cacheTexture->getTextureId();
359                caches.activeTexture(0);
360                glBindTexture(GL_TEXTURE_2D, lastTextureId);
361            }
362#if DEBUG_FONT_RENDERER
363            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
364                    i, x, y, width, height);
365#endif
366            glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
367                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
368            cacheTexture->setDirty(false);
369        }
370    }
371
372    mUploadTexture = false;
373}
374
375void FontRenderer::issueDrawCommand() {
376    updateDrawParams();
377    checkTextureUpdate();
378
379    Caches& caches = Caches::getInstance();
380    caches.bindIndicesBuffer(mIndexBufferID);
381    if (!mDrawn) {
382        float* buffer = mTextMesh;
383        int offset = 2;
384
385        bool force = caches.unbindMeshBuffer();
386        caches.bindPositionVertexPointer(force, buffer);
387        caches.bindTexCoordsVertexPointer(force, buffer + offset);
388    }
389
390    for (uint32_t i = 0; i < mDrawOffsets.size(); i++) {
391        uint16_t* offset = mDrawOffsets[i];
392        uint32_t count = mDrawCounts[i];
393        CacheTexture* texture = mDrawCacheTextures[i];
394
395        caches.activeTexture(0);
396        glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
397
398        texture->setLinearFiltering(mLinearFiltering, false);
399
400        glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset);
401    }
402
403    mDrawn = true;
404
405    mCurrentQuadIndex = 0;
406    mLastQuadIndex = 0;
407    mDrawOffsets.clear();
408    mDrawCounts.clear();
409    mDrawCacheTextures.clear();
410}
411
412void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
413        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
414        float x4, float y4, float u4, float v4, CacheTexture* texture) {
415    if (texture != mCurrentCacheTexture) {
416        updateDrawParams();
417        // Now use the new texture id
418        mCurrentCacheTexture = texture;
419    }
420
421    const uint32_t vertsPerQuad = 4;
422    const uint32_t floatsPerVert = 4;
423    float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
424
425    (*currentPos++) = x1;
426    (*currentPos++) = y1;
427    (*currentPos++) = u1;
428    (*currentPos++) = v1;
429
430    (*currentPos++) = x2;
431    (*currentPos++) = y2;
432    (*currentPos++) = u2;
433    (*currentPos++) = v2;
434
435    (*currentPos++) = x3;
436    (*currentPos++) = y3;
437    (*currentPos++) = u3;
438    (*currentPos++) = v3;
439
440    (*currentPos++) = x4;
441    (*currentPos++) = y4;
442    (*currentPos++) = u4;
443    (*currentPos++) = v4;
444
445    mCurrentQuadIndex++;
446}
447
448void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
449        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
450        float x4, float y4, float u4, float v4, CacheTexture* texture) {
451
452    if (mClip &&
453            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
454        return;
455    }
456
457    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
458
459    if (mBounds) {
460        mBounds->left = fmin(mBounds->left, x1);
461        mBounds->top = fmin(mBounds->top, y3);
462        mBounds->right = fmax(mBounds->right, x3);
463        mBounds->bottom = fmax(mBounds->bottom, y1);
464    }
465
466    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
467        issueDrawCommand();
468    }
469}
470
471void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
472        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
473        float x4, float y4, float u4, float v4, CacheTexture* texture) {
474
475    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
476
477    if (mBounds) {
478        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
479        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
480        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
481        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
482    }
483
484    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
485        issueDrawCommand();
486    }
487}
488
489void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
490    mCurrentFont = Font::create(this, paint, matrix);
491}
492
493FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
494        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
495    checkInit();
496
497    if (!mCurrentFont) {
498        DropShadow image;
499        image.width = 0;
500        image.height = 0;
501        image.image = NULL;
502        image.penX = 0;
503        image.penY = 0;
504        return image;
505    }
506
507    mDrawn = false;
508    mClip = NULL;
509    mBounds = NULL;
510
511    Rect bounds;
512    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
513
514    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
515    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
516    uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
517
518    for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
519        dataBuffer[i] = 0;
520    }
521
522    int penX = radius - bounds.left;
523    int penY = radius - bounds.bottom;
524
525    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
526            Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
527    blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
528
529    DropShadow image;
530    image.width = paddedWidth;
531    image.height = paddedHeight;
532    image.image = dataBuffer;
533    image.penX = penX;
534    image.penY = penY;
535
536    return image;
537}
538
539void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
540    checkInit();
541
542    mDrawn = false;
543    mBounds = bounds;
544    mClip = clip;
545}
546
547void FontRenderer::finishRender() {
548    mBounds = NULL;
549    mClip = NULL;
550
551    if (mCurrentQuadIndex != 0) {
552        issueDrawCommand();
553    }
554}
555
556void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
557    Font* font = Font::create(this, paint, matrix);
558    font->precache(paint, text, numGlyphs);
559}
560
561bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
562        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
563        const float* positions, Rect* bounds) {
564    if (!mCurrentFont) {
565        ALOGE("No font set");
566        return false;
567    }
568
569    initRender(clip, bounds);
570    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
571    finishRender();
572
573    return mDrawn;
574}
575
576bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
577        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
578        float hOffset, float vOffset, Rect* bounds) {
579    if (!mCurrentFont) {
580        ALOGE("No font set");
581        return false;
582    }
583
584    initRender(clip, bounds);
585    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
586    finishRender();
587
588    return mDrawn;
589}
590
591void FontRenderer::removeFont(const Font* font) {
592    mActiveFonts.remove(font->getDescription());
593
594    if (mCurrentFont == font) {
595        mCurrentFont = NULL;
596    }
597}
598
599void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
600    // Compute gaussian weights for the blur
601    // e is the euler's number
602    float e = 2.718281828459045f;
603    float pi = 3.1415926535897932f;
604    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
605    // x is of the form [-radius .. 0 .. radius]
606    // and sigma varies with radius.
607    // Based on some experimental radius values and sigma's
608    // we approximately fit sigma = f(radius) as
609    // sigma = radius * 0.3  + 0.6
610    // The larger the radius gets, the more our gaussian blur
611    // will resemble a box blur since with large sigma
612    // the gaussian curve begins to lose its shape
613    float sigma = 0.3f * (float) radius + 0.6f;
614
615    // Now compute the coefficints
616    // We will store some redundant values to save some math during
617    // the blur calculations
618    // precompute some values
619    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
620    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
621
622    float normalizeFactor = 0.0f;
623    for (int32_t r = -radius; r <= radius; r ++) {
624        float floatR = (float) r;
625        weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
626        normalizeFactor += weights[r + radius];
627    }
628
629    //Now we need to normalize the weights because all our coefficients need to add up to one
630    normalizeFactor = 1.0f / normalizeFactor;
631    for (int32_t r = -radius; r <= radius; r ++) {
632        weights[r + radius] *= normalizeFactor;
633    }
634}
635
636void FontRenderer::horizontalBlur(float* weights, int32_t radius,
637        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
638    float blurredPixel = 0.0f;
639    float currentPixel = 0.0f;
640
641    for (int32_t y = 0; y < height; y ++) {
642
643        const uint8_t* input = source + y * width;
644        uint8_t* output = dest + y * width;
645
646        for (int32_t x = 0; x < width; x ++) {
647            blurredPixel = 0.0f;
648            const float* gPtr = weights;
649            // Optimization for non-border pixels
650            if (x > radius && x < (width - radius)) {
651                const uint8_t *i = input + (x - radius);
652                for (int r = -radius; r <= radius; r ++) {
653                    currentPixel = (float) (*i);
654                    blurredPixel += currentPixel * gPtr[0];
655                    gPtr++;
656                    i++;
657                }
658            } else {
659                for (int32_t r = -radius; r <= radius; r ++) {
660                    // Stepping left and right away from the pixel
661                    int validW = x + r;
662                    if (validW < 0) {
663                        validW = 0;
664                    }
665                    if (validW > width - 1) {
666                        validW = width - 1;
667                    }
668
669                    currentPixel = (float) input[validW];
670                    blurredPixel += currentPixel * gPtr[0];
671                    gPtr++;
672                }
673            }
674            *output = (uint8_t)blurredPixel;
675            output ++;
676        }
677    }
678}
679
680void FontRenderer::verticalBlur(float* weights, int32_t radius,
681        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
682    float blurredPixel = 0.0f;
683    float currentPixel = 0.0f;
684
685    for (int32_t y = 0; y < height; y ++) {
686        uint8_t* output = dest + y * width;
687
688        for (int32_t x = 0; x < width; x ++) {
689            blurredPixel = 0.0f;
690            const float* gPtr = weights;
691            const uint8_t* input = source + x;
692            // Optimization for non-border pixels
693            if (y > radius && y < (height - radius)) {
694                const uint8_t *i = input + ((y - radius) * width);
695                for (int32_t r = -radius; r <= radius; r ++) {
696                    currentPixel = (float)(*i);
697                    blurredPixel += currentPixel * gPtr[0];
698                    gPtr++;
699                    i += width;
700                }
701            } else {
702                for (int32_t r = -radius; r <= radius; r ++) {
703                    int validH = y + r;
704                    // Clamp to zero and width
705                    if (validH < 0) {
706                        validH = 0;
707                    }
708                    if (validH > height - 1) {
709                        validH = height - 1;
710                    }
711
712                    const uint8_t *i = input + validH * width;
713                    currentPixel = (float) (*i);
714                    blurredPixel += currentPixel * gPtr[0];
715                    gPtr++;
716                }
717            }
718            *output = (uint8_t) blurredPixel;
719            output++;
720        }
721    }
722}
723
724
725void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
726    float *gaussian = new float[2 * radius + 1];
727    computeGaussianWeights(gaussian, radius);
728
729    uint8_t* scratch = new uint8_t[width * height];
730
731    horizontalBlur(gaussian, radius, image, scratch, width, height);
732    verticalBlur(gaussian, radius, scratch, image, width, height);
733
734    delete[] gaussian;
735    delete[] scratch;
736}
737
738}; // namespace uirenderer
739}; // namespace android
740