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