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