FontRenderer.cpp revision e4d9a01bfc7451afff1ed399a5801c7aa2af2831
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/Functor.h>
25#include <utils/Log.h>
26
27#ifdef ANDROID_ENABLE_RENDERSCRIPT
28#include <RenderScript.h>
29#endif
30
31#include "utils/Blur.h"
32#include "utils/Timing.h"
33
34#include "Caches.h"
35#include "Debug.h"
36#include "FontRenderer.h"
37#include "Rect.h"
38
39namespace android {
40namespace uirenderer {
41
42// blur inputs smaller than this constant will bypass renderscript
43#define RS_MIN_INPUT_CUTOFF 10000
44
45///////////////////////////////////////////////////////////////////////////////
46// FontRenderer
47///////////////////////////////////////////////////////////////////////////////
48
49static bool sLogFontRendererCreate = true;
50
51FontRenderer::FontRenderer() :
52        mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) {
53
54    if (sLogFontRendererCreate) {
55        INIT_LOGD("Creating FontRenderer");
56    }
57
58    mGammaTable = NULL;
59    mInitialized = false;
60    mMaxNumberOfQuads = 1024;
61
62    mCurrentCacheTexture = NULL;
63
64    mLinearFiltering = false;
65
66    mIndexBufferID = 0;
67
68    mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
69    mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
70    mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
71    mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
72
73    char property[PROPERTY_VALUE_MAX];
74    if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) {
75        mSmallCacheWidth = atoi(property);
76    }
77
78    if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) {
79        mSmallCacheHeight = atoi(property);
80    }
81
82    if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) {
83        mLargeCacheWidth = atoi(property);
84    }
85
86    if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) {
87        mLargeCacheHeight = atoi(property);
88    }
89
90    uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
91    mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth;
92    mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight;
93    mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth;
94    mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight;
95
96    if (sLogFontRendererCreate) {
97        INIT_LOGD("  Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
98                mSmallCacheWidth, mSmallCacheHeight,
99                mLargeCacheWidth, mLargeCacheHeight >> 1,
100                mLargeCacheWidth, mLargeCacheHeight >> 1,
101                mLargeCacheWidth, mLargeCacheHeight);
102    }
103
104    sLogFontRendererCreate = false;
105}
106
107FontRenderer::~FontRenderer() {
108    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
109        delete mCacheTextures[i];
110    }
111    mCacheTextures.clear();
112
113    if (mInitialized) {
114        // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
115        Caches::getInstance().unbindIndicesBuffer();
116        glDeleteBuffers(1, &mIndexBufferID);
117    }
118
119    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
120    while (it.next()) {
121        delete it.value();
122    }
123    mActiveFonts.clear();
124}
125
126void FontRenderer::flushAllAndInvalidate() {
127    issueDrawCommand();
128
129    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
130    while (it.next()) {
131        it.value()->invalidateTextureCache();
132    }
133
134    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
135        mCacheTextures[i]->init();
136    }
137
138#if DEBUG_FONT_RENDERER
139    uint16_t totalGlyphs = 0;
140    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
141        totalGlyphs += mCacheTextures[i]->getGlyphCount();
142        // Erase caches, just as a debugging facility
143        if (mCacheTextures[i]->getTexture()) {
144            memset(mCacheTextures[i]->getTexture(), 0,
145                    mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight());
146        }
147    }
148    ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs);
149#endif
150}
151
152void FontRenderer::flushLargeCaches() {
153    // Start from 1; don't deallocate smallest/default texture
154    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
155        CacheTexture* cacheTexture = mCacheTextures[i];
156        if (cacheTexture->getTexture()) {
157            cacheTexture->init();
158            LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
159            while (it.next()) {
160                it.value()->invalidateTextureCache(cacheTexture);
161            }
162            cacheTexture->releaseTexture();
163        }
164    }
165}
166
167CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
168        uint32_t* startX, uint32_t* startY) {
169    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
170        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
171            return mCacheTextures[i];
172        }
173    }
174    // Could not fit glyph into current cache textures
175    return NULL;
176}
177
178void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
179        uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
180    checkInit();
181
182    // If the glyph bitmap is empty let's assum the glyph is valid
183    // so we can avoid doing extra work later on
184    if (glyph.fWidth == 0 || glyph.fHeight == 0) {
185        cachedGlyph->mIsValid = true;
186        cachedGlyph->mCacheTexture = NULL;
187        return;
188    }
189
190    cachedGlyph->mIsValid = false;
191
192    // If the glyph is too tall, don't cache it
193    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
194                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
195        ALOGE("Font size too large to fit in cache. width, height = %i, %i",
196                (int) glyph.fWidth, (int) glyph.fHeight);
197        return;
198    }
199
200    // Now copy the bitmap into the cache texture
201    uint32_t startX = 0;
202    uint32_t startY = 0;
203
204    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
205
206    if (!cacheTexture) {
207        if (!precaching) {
208            // If the new glyph didn't fit and we are not just trying to precache it,
209            // clear out the cache and try again
210            flushAllAndInvalidate();
211            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
212        }
213
214        if (!cacheTexture) {
215            // either the glyph didn't fit or we're precaching and will cache it when we draw
216            return;
217        }
218    }
219
220    cachedGlyph->mCacheTexture = cacheTexture;
221
222    *retOriginX = startX;
223    *retOriginY = startY;
224
225    uint32_t endX = startX + glyph.fWidth;
226    uint32_t endY = startY + glyph.fHeight;
227
228    uint32_t cacheWidth = cacheTexture->getWidth();
229
230    if (!cacheTexture->getTexture()) {
231        Caches::getInstance().activeTexture(0);
232        // Large-glyph texture memory is allocated only as needed
233        cacheTexture->allocateTexture();
234    }
235    if (!cacheTexture->mesh()) {
236        cacheTexture->allocateMesh();
237    }
238
239    // Tells us whether the glyphs is B&W (1 bit per pixel)
240    // or anti-aliased (8 bits per pixel)
241    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
242
243    uint8_t* cacheBuffer = cacheTexture->getTexture();
244    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
245
246    // Copy the glyph image, taking the mask format into account
247    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
248    int stride = glyph.rowBytes();
249
250    uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
251    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
252
253    switch (format) {
254        case SkMask::kA8_Format: {
255            if (mGammaTable) {
256                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
257                    row = cacheY * cacheWidth;
258                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
259                    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
260                        uint8_t tempCol = bitmapBuffer[bY + bX];
261                        cacheBuffer[row + cacheX] = mGammaTable[tempCol];
262                    }
263                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
264                }
265            } else {
266                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
267                    row = cacheY * cacheWidth;
268                    memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
269                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
270                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
271                }
272            }
273            break;
274        }
275        case SkMask::kBW_Format: {
276            static const uint8_t COLORS[2] = { 0, 255 };
277
278            for (cacheY = startY; cacheY < endY; cacheY++) {
279                cacheX = startX;
280                int rowBytes = stride;
281                uint8_t* buffer = bitmapBuffer;
282
283                row = cacheY * cacheWidth;
284                cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
285                while (--rowBytes >= 0) {
286                    uint8_t b = *buffer++;
287                    for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) {
288                        cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1];
289                    }
290                }
291                cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
292
293                bitmapBuffer += stride;
294            }
295            break;
296        }
297        default:
298            ALOGW("Unkown glyph format: 0x%x", format);
299            break;
300    }
301
302    row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
303    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
304
305    cachedGlyph->mIsValid = true;
306}
307
308CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
309    CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads);
310
311    if (allocate) {
312        Caches::getInstance().activeTexture(0);
313        cacheTexture->allocateTexture();
314        cacheTexture->allocateMesh();
315    }
316
317    return cacheTexture;
318}
319
320void FontRenderer::initTextTexture() {
321    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
322        delete mCacheTextures[i];
323    }
324    mCacheTextures.clear();
325
326    mUploadTexture = false;
327    mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
328    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
329    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
330    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
331    mCurrentCacheTexture = mCacheTextures[0];
332}
333
334// Avoid having to reallocate memory and render quad by quad
335void FontRenderer::initVertexArrayBuffers() {
336    uint32_t numIndices = mMaxNumberOfQuads * 6;
337    uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
338    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
339
340    // Four verts, two triangles , six indices per quad
341    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
342        int i6 = i * 6;
343        int i4 = i * 4;
344
345        indexBufferData[i6 + 0] = i4 + 0;
346        indexBufferData[i6 + 1] = i4 + 1;
347        indexBufferData[i6 + 2] = i4 + 2;
348
349        indexBufferData[i6 + 3] = i4 + 0;
350        indexBufferData[i6 + 4] = i4 + 2;
351        indexBufferData[i6 + 5] = i4 + 3;
352    }
353
354    glGenBuffers(1, &mIndexBufferID);
355    Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
356    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
357
358    free(indexBufferData);
359}
360
361// We don't want to allocate anything unless we actually draw text
362void FontRenderer::checkInit() {
363    if (mInitialized) {
364        return;
365    }
366
367    initTextTexture();
368    initVertexArrayBuffers();
369
370    mInitialized = true;
371}
372
373void FontRenderer::checkTextureUpdate() {
374    if (!mUploadTexture) {
375        return;
376    }
377
378    Caches& caches = Caches::getInstance();
379    GLuint lastTextureId = 0;
380    // Iterate over all the cache textures and see which ones need to be updated
381    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
382        CacheTexture* cacheTexture = mCacheTextures[i];
383        if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
384            // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
385            // of data. So expand the dirty rect to the encompassing horizontal stripe.
386            const Rect* dirtyRect = cacheTexture->getDirtyRect();
387            uint32_t x = 0;
388            uint32_t y = dirtyRect->top;
389            uint32_t width = cacheTexture->getWidth();
390            uint32_t height = dirtyRect->getHeight();
391            void* textureData = cacheTexture->getTexture() + y * width;
392
393            if (cacheTexture->getTextureId() != lastTextureId) {
394                lastTextureId = cacheTexture->getTextureId();
395                caches.activeTexture(0);
396                glBindTexture(GL_TEXTURE_2D, lastTextureId);
397            }
398#if DEBUG_FONT_RENDERER
399            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
400                    i, x, y, width, height);
401#endif
402            glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
403                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
404            cacheTexture->setDirty(false);
405        }
406    }
407
408    mUploadTexture = false;
409}
410
411void FontRenderer::issueDrawCommand() {
412    bool first = true;
413    bool force = false;
414
415    GLuint lastId = 0;
416    Caches& caches = Caches::getInstance();
417
418    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
419        CacheTexture* texture = mCacheTextures[i];
420        if (texture->canDraw()) {
421            if (first) {
422                if (mFunctor) (*mFunctor)(0, NULL);
423
424                checkTextureUpdate();
425                caches.bindIndicesBuffer(mIndexBufferID);
426
427                if (!mDrawn) {
428                    // If returns true, a VBO was bound and we must
429                    // rebind our vertex attrib pointers even if
430                    // they have the same values as the current pointers
431                    force = caches.unbindMeshBuffer();
432                }
433
434                caches.activeTexture(0);
435                first = false;
436            }
437
438            glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
439            texture->setLinearFiltering(mLinearFiltering, false);
440
441            TextureVertex* mesh = texture->mesh();
442            caches.bindPositionVertexPointer(force, &mesh[0].position[0]);
443            caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]);
444            force = false;
445
446            glDrawElements(GL_TRIANGLES, texture->meshElementCount(),
447                    GL_UNSIGNED_SHORT, texture->indices());
448
449            texture->resetMesh();
450        }
451    }
452
453    mDrawn = true;
454}
455
456void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
457        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
458        float x4, float y4, float u4, float v4, CacheTexture* texture) {
459    if (texture != mCurrentCacheTexture) {
460        // Now use the new texture id
461        mCurrentCacheTexture = texture;
462    }
463
464    mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2,
465            x3, y3, u3, v3, x4, y4, u4, v4);
466}
467
468void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
469        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
470        float x4, float y4, float u4, float v4, CacheTexture* texture) {
471
472    if (mClip &&
473            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
474        return;
475    }
476
477    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
478
479    if (mBounds) {
480        mBounds->left = fmin(mBounds->left, x1);
481        mBounds->top = fmin(mBounds->top, y3);
482        mBounds->right = fmax(mBounds->right, x3);
483        mBounds->bottom = fmax(mBounds->bottom, y1);
484    }
485
486    if (mCurrentCacheTexture->endOfMesh()) {
487        issueDrawCommand();
488    }
489}
490
491void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
492        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
493        float x4, float y4, float u4, float v4, CacheTexture* texture) {
494
495    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
496
497    if (mBounds) {
498        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
499        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
500        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
501        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
502    }
503
504    if (mCurrentCacheTexture->endOfMesh()) {
505        issueDrawCommand();
506    }
507}
508
509void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
510    mCurrentFont = Font::create(this, paint, matrix);
511}
512
513FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
514        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
515    checkInit();
516
517    if (!mCurrentFont) {
518        DropShadow image;
519        image.width = 0;
520        image.height = 0;
521        image.image = NULL;
522        image.penX = 0;
523        image.penY = 0;
524        return image;
525    }
526
527    mDrawn = false;
528    mClip = NULL;
529    mBounds = NULL;
530
531    Rect bounds;
532    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
533
534    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
535    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
536
537#ifdef ANDROID_ENABLE_RENDERSCRIPT
538    // Align buffers for renderscript usage
539    if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) {
540        paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT;
541    }
542    int size = paddedWidth * paddedHeight;
543    uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size);
544#else
545    int size = paddedWidth * paddedHeight;
546    uint8_t* dataBuffer = (uint8_t*) malloc(size);
547#endif
548
549    memset(dataBuffer, 0, size);
550
551    int penX = radius - bounds.left;
552    int penY = radius - bounds.bottom;
553
554    if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
555        // text has non-whitespace, so draw and blur to create the shadow
556        // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
557        // TODO: don't draw pure whitespace in the first place, and avoid needing this check
558        mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
559                Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
560
561        blurImage(&dataBuffer, paddedWidth, paddedHeight, radius);
562    }
563
564    DropShadow image;
565    image.width = paddedWidth;
566    image.height = paddedHeight;
567    image.image = dataBuffer;
568    image.penX = penX;
569    image.penY = penY;
570
571    return image;
572}
573
574void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) {
575    checkInit();
576
577    mDrawn = false;
578    mBounds = bounds;
579    mFunctor = functor;
580    mClip = clip;
581}
582
583void FontRenderer::finishRender() {
584    mBounds = NULL;
585    mClip = NULL;
586
587    issueDrawCommand();
588}
589
590void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
591    Font* font = Font::create(this, paint, matrix);
592    font->precache(paint, text, numGlyphs);
593}
594
595bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
596        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
597        const float* positions, Rect* bounds, Functor* functor) {
598    if (!mCurrentFont) {
599        ALOGE("No font set");
600        return false;
601    }
602
603    initRender(clip, bounds, functor);
604    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
605    finishRender();
606
607    return mDrawn;
608}
609
610bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
611        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
612        float hOffset, float vOffset, Rect* bounds) {
613    if (!mCurrentFont) {
614        ALOGE("No font set");
615        return false;
616    }
617
618    initRender(clip, bounds, NULL);
619    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
620    finishRender();
621
622    return mDrawn;
623}
624
625void FontRenderer::removeFont(const Font* font) {
626    mActiveFonts.remove(font->getDescription());
627
628    if (mCurrentFont == font) {
629        mCurrentFont = NULL;
630    }
631}
632
633void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
634#ifdef ANDROID_ENABLE_RENDERSCRIPT
635    if (width * height * radius >= RS_MIN_INPUT_CUTOFF) {
636        uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
637
638        if (mRs.get() == 0) {
639            mRs = new RSC::RS();
640            if (!mRs->init(true, true)) {
641                ALOGE("blur RS failed to init");
642            }
643
644            mRsElement = RSC::Element::A_8(mRs);
645            mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
646        }
647
648        sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
649        sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
650                RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image);
651        sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
652                RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage);
653
654        mRsScript->setRadius(radius);
655        mRsScript->blur(ain, aout);
656
657        // replace the original image's pointer, avoiding a copy back to the original buffer
658        free(*image);
659        *image = outImage;
660
661        return;
662    }
663#endif
664
665    float *gaussian = new float[2 * radius + 1];
666    Blur::generateGaussianWeights(gaussian, radius);
667
668    uint8_t* scratch = new uint8_t[width * height];
669    Blur::horizontal(gaussian, radius, *image, scratch, width, height);
670    Blur::vertical(gaussian, radius, scratch, *image, width, height);
671
672    delete[] gaussian;
673    delete[] scratch;
674}
675
676}; // namespace uirenderer
677}; // namespace android
678