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