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