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