FontRenderer.cpp revision 115096f50a560e64a7f95d37686d4861042c7aeb
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    mCurrentQuadIndex = 0;
59    mLastQuadIndex = 0;
60
61    mTextMesh = NULL;
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        delete[] mTextMesh;
119    }
120
121    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
122    while (it.next()) {
123        delete it.value();
124    }
125    mActiveFonts.clear();
126}
127
128void FontRenderer::flushAllAndInvalidate() {
129    if (mCurrentQuadIndex != 0) {
130        issueDrawCommand();
131    }
132
133    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
134    while (it.next()) {
135        it.value()->invalidateTextureCache();
136    }
137
138    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
139        mCacheTextures[i]->init();
140    }
141
142#if DEBUG_FONT_RENDERER
143    uint16_t totalGlyphs = 0;
144    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
145        totalGlyphs += mCacheTextures[i]->getGlyphCount();
146        // Erase caches, just as a debugging facility
147        if (mCacheTextures[i]->getTexture()) {
148            memset(mCacheTextures[i]->getTexture(), 0,
149                    mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight());
150        }
151    }
152    ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs);
153#endif
154}
155
156void FontRenderer::flushLargeCaches() {
157    // Start from 1; don't deallocate smallest/default texture
158    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
159        CacheTexture* cacheTexture = mCacheTextures[i];
160        if (cacheTexture->getTexture()) {
161            cacheTexture->init();
162            LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
163            while (it.next()) {
164                it.value()->invalidateTextureCache(cacheTexture);
165            }
166            cacheTexture->releaseTexture();
167        }
168    }
169}
170
171CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
172        uint32_t* startX, uint32_t* startY) {
173    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
174        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
175            return mCacheTextures[i];
176        }
177    }
178    // Could not fit glyph into current cache textures
179    return NULL;
180}
181
182void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
183        uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
184    checkInit();
185
186    // If the glyph bitmap is empty let's assum the glyph is valid
187    // so we can avoid doing extra work later on
188    if (glyph.fWidth == 0 || glyph.fHeight == 0) {
189        cachedGlyph->mIsValid = true;
190        cachedGlyph->mCacheTexture = NULL;
191        return;
192    }
193
194    cachedGlyph->mIsValid = false;
195
196    // If the glyph is too tall, don't cache it
197    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
198                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
199        ALOGE("Font size too large to fit in cache. width, height = %i, %i",
200                (int) glyph.fWidth, (int) glyph.fHeight);
201        return;
202    }
203
204    // Now copy the bitmap into the cache texture
205    uint32_t startX = 0;
206    uint32_t startY = 0;
207
208    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
209
210    if (!cacheTexture) {
211        if (!precaching) {
212            // If the new glyph didn't fit and we are not just trying to precache it,
213            // clear out the cache and try again
214            flushAllAndInvalidate();
215            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
216        }
217
218        if (!cacheTexture) {
219            // either the glyph didn't fit or we're precaching and will cache it when we draw
220            return;
221        }
222    }
223
224    cachedGlyph->mCacheTexture = cacheTexture;
225
226    *retOriginX = startX;
227    *retOriginY = startY;
228
229    uint32_t endX = startX + glyph.fWidth;
230    uint32_t endY = startY + glyph.fHeight;
231
232    uint32_t cacheWidth = cacheTexture->getWidth();
233
234    if (!cacheTexture->getTexture()) {
235        Caches::getInstance().activeTexture(0);
236        // Large-glyph texture memory is allocated only as needed
237        cacheTexture->allocateTexture();
238    }
239
240    // Tells us whether the glyphs is B&W (1 bit per pixel)
241    // or anti-aliased (8 bits per pixel)
242    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
243
244    uint8_t* cacheBuffer = cacheTexture->getTexture();
245    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
246
247    // Copy the glyph image, taking the mask format into account
248    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
249    int stride = glyph.rowBytes();
250
251    uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
252    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
253
254    switch (format) {
255        case SkMask::kA8_Format: {
256            if (mGammaTable) {
257                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
258                    row = cacheY * cacheWidth;
259                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
260                    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
261                        uint8_t tempCol = bitmapBuffer[bY + bX];
262                        cacheBuffer[row + cacheX] = mGammaTable[tempCol];
263                    }
264                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
265                }
266            } else {
267                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
268                    row = cacheY * cacheWidth;
269                    memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
270                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
271                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
272                }
273            }
274            break;
275        }
276        case SkMask::kBW_Format: {
277            static const uint8_t COLORS[2] = { 0, 255 };
278
279            for (cacheY = startY; cacheY < endY; cacheY++) {
280                cacheX = startX;
281                int rowBytes = stride;
282                uint8_t* buffer = bitmapBuffer;
283
284                row = cacheY * cacheWidth;
285                cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
286                while (--rowBytes >= 0) {
287                    uint8_t b = *buffer++;
288                    for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) {
289                        cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1];
290                    }
291                }
292                cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
293
294                bitmapBuffer += stride;
295            }
296            break;
297        }
298        default:
299            ALOGW("Unkown glyph format: 0x%x", format);
300            break;
301    }
302
303    row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
304    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
305
306    cachedGlyph->mIsValid = true;
307}
308
309CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
310    CacheTexture* cacheTexture = new CacheTexture(width, height);
311
312    if (allocate) {
313        Caches::getInstance().activeTexture(0);
314        cacheTexture->allocateTexture();
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    uint32_t coordSize = 2;
361    uint32_t uvSize = 2;
362    uint32_t vertsPerQuad = 4;
363    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
364    mTextMesh = new float[vertexBufferSize];
365}
366
367// We don't want to allocate anything unless we actually draw text
368void FontRenderer::checkInit() {
369    if (mInitialized) {
370        return;
371    }
372
373    initTextTexture();
374    initVertexArrayBuffers();
375
376    mInitialized = true;
377}
378
379void FontRenderer::updateDrawParams() {
380    if (mCurrentQuadIndex != mLastQuadIndex) {
381        uint16_t* offset = (uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6);
382        uint32_t count = mCurrentQuadIndex - mLastQuadIndex;
383        mDrawBatch.add(TextBatch(offset, count, mCurrentCacheTexture));
384        mLastQuadIndex = mCurrentQuadIndex;
385    }
386}
387
388void FontRenderer::checkTextureUpdate() {
389    if (!mUploadTexture) {
390        return;
391    }
392
393    Caches& caches = Caches::getInstance();
394    GLuint lastTextureId = 0;
395    // Iterate over all the cache textures and see which ones need to be updated
396    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
397        CacheTexture* cacheTexture = mCacheTextures[i];
398        if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
399            // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
400            // of data. So expand the dirty rect to the encompassing horizontal stripe.
401            const Rect* dirtyRect = cacheTexture->getDirtyRect();
402            uint32_t x = 0;
403            uint32_t y = dirtyRect->top;
404            uint32_t width = cacheTexture->getWidth();
405            uint32_t height = dirtyRect->getHeight();
406            void* textureData = cacheTexture->getTexture() + y * width;
407
408            if (cacheTexture->getTextureId() != lastTextureId) {
409                lastTextureId = cacheTexture->getTextureId();
410                caches.activeTexture(0);
411                glBindTexture(GL_TEXTURE_2D, lastTextureId);
412            }
413#if DEBUG_FONT_RENDERER
414            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
415                    i, x, y, width, height);
416#endif
417            glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
418                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
419            cacheTexture->setDirty(false);
420        }
421    }
422
423    mUploadTexture = false;
424}
425
426void FontRenderer::issueDrawCommand() {
427    updateDrawParams();
428    checkTextureUpdate();
429
430    Caches& caches = Caches::getInstance();
431    caches.bindIndicesBuffer(mIndexBufferID);
432    if (!mDrawn) {
433        float* buffer = mTextMesh;
434        int offset = 2;
435
436        bool force = caches.unbindMeshBuffer();
437        caches.bindPositionVertexPointer(force, buffer);
438        caches.bindTexCoordsVertexPointer(force, buffer + offset);
439    }
440
441    caches.activeTexture(0);
442    GLuint lastId = 0;
443
444    for (uint32_t i = 0; i < mDrawBatch.size(); i++) {
445        const TextBatch& batch = mDrawBatch[i];
446
447        GLuint id = batch.texture->getTextureId();
448        if (id != lastId) {
449            glBindTexture(GL_TEXTURE_2D, id);
450            batch.texture->setLinearFiltering(mLinearFiltering, false);
451            lastId = id;
452        }
453
454        glDrawElements(GL_TRIANGLES, batch.count * 6, GL_UNSIGNED_SHORT, batch.offset);
455    }
456
457    mDrawn = true;
458
459    mCurrentQuadIndex = 0;
460    mLastQuadIndex = 0;
461    mDrawBatch.clear();
462}
463
464void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
465        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
466        float x4, float y4, float u4, float v4, CacheTexture* texture) {
467    if (texture != mCurrentCacheTexture) {
468        updateDrawParams();
469        // Now use the new texture id
470        mCurrentCacheTexture = texture;
471    }
472
473    const uint32_t vertsPerQuad = 4;
474    const uint32_t floatsPerVert = 4;
475    float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
476
477    (*currentPos++) = x1;
478    (*currentPos++) = y1;
479    (*currentPos++) = u1;
480    (*currentPos++) = v1;
481
482    (*currentPos++) = x2;
483    (*currentPos++) = y2;
484    (*currentPos++) = u2;
485    (*currentPos++) = v2;
486
487    (*currentPos++) = x3;
488    (*currentPos++) = y3;
489    (*currentPos++) = u3;
490    (*currentPos++) = v3;
491
492    (*currentPos++) = x4;
493    (*currentPos++) = y4;
494    (*currentPos++) = u4;
495    (*currentPos++) = v4;
496
497    mCurrentQuadIndex++;
498}
499
500void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
501        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
502        float x4, float y4, float u4, float v4, CacheTexture* texture) {
503
504    if (mClip &&
505            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
506        return;
507    }
508
509    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
510
511    if (mBounds) {
512        mBounds->left = fmin(mBounds->left, x1);
513        mBounds->top = fmin(mBounds->top, y3);
514        mBounds->right = fmax(mBounds->right, x3);
515        mBounds->bottom = fmax(mBounds->bottom, y1);
516    }
517
518    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
519        issueDrawCommand();
520    }
521}
522
523void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
524        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
525        float x4, float y4, float u4, float v4, CacheTexture* texture) {
526
527    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
528
529    if (mBounds) {
530        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
531        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
532        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
533        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
534    }
535
536    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
537        issueDrawCommand();
538    }
539}
540
541void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
542    mCurrentFont = Font::create(this, paint, matrix);
543}
544
545FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
546        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
547    checkInit();
548
549    if (!mCurrentFont) {
550        DropShadow image;
551        image.width = 0;
552        image.height = 0;
553        image.image = NULL;
554        image.penX = 0;
555        image.penY = 0;
556        return image;
557    }
558
559    mDrawn = false;
560    mClip = NULL;
561    mBounds = NULL;
562
563    Rect bounds;
564    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
565
566    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
567    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
568
569    // Align buffers for renderscript usage
570    if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) {
571        paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT;
572    }
573
574    int size = paddedWidth * paddedHeight;
575    uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size);
576    memset(dataBuffer, 0, size);
577
578    int penX = radius - bounds.left;
579    int penY = radius - bounds.bottom;
580
581    if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
582        // text has non-whitespace, so draw and blur to create the shadow
583        // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
584        // TODO: don't draw pure whitespace in the first place, and avoid needing this check
585        mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
586                Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
587
588        blurImage(&dataBuffer, paddedWidth, paddedHeight, radius);
589    }
590
591    DropShadow image;
592    image.width = paddedWidth;
593    image.height = paddedHeight;
594    image.image = dataBuffer;
595    image.penX = penX;
596    image.penY = penY;
597
598    return image;
599}
600
601void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
602    checkInit();
603
604    mDrawn = false;
605    mBounds = bounds;
606    mClip = clip;
607}
608
609void FontRenderer::finishRender() {
610    mBounds = NULL;
611    mClip = NULL;
612
613    if (mCurrentQuadIndex != 0) {
614        issueDrawCommand();
615    }
616}
617
618void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
619    Font* font = Font::create(this, paint, matrix);
620    font->precache(paint, text, numGlyphs);
621}
622
623bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
624        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
625        const float* positions, Rect* bounds) {
626    if (!mCurrentFont) {
627        ALOGE("No font set");
628        return false;
629    }
630
631    initRender(clip, bounds);
632    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
633    finishRender();
634
635    return mDrawn;
636}
637
638bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
639        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
640        float hOffset, float vOffset, Rect* bounds) {
641    if (!mCurrentFont) {
642        ALOGE("No font set");
643        return false;
644    }
645
646    initRender(clip, bounds);
647    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
648    finishRender();
649
650    return mDrawn;
651}
652
653void FontRenderer::removeFont(const Font* font) {
654    mActiveFonts.remove(font->getDescription());
655
656    if (mCurrentFont == font) {
657        mCurrentFont = NULL;
658    }
659}
660
661void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
662    if (width * height * radius < RS_MIN_INPUT_CUTOFF) {
663        float *gaussian = new float[2 * radius + 1];
664        Blur::generateGaussianWeights(gaussian, radius);
665
666        uint8_t* scratch = new uint8_t[width * height];
667        Blur::horizontal(gaussian, radius, *image, scratch, width, height);
668        Blur::vertical(gaussian, radius, scratch, *image, width, height);
669
670        delete[] gaussian;
671        delete[] scratch;
672        return;
673    }
674
675    uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
676
677    if (mRs.get() == 0) {
678        mRs = new RSC::RS();
679        if (!mRs->init(true, true)) {
680            ALOGE("blur RS failed to init");
681        }
682
683        mRsElement = RSC::Element::A_8(mRs);
684        mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
685    }
686
687    sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
688    sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
689            RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image);
690    sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
691            RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage);
692
693    mRsScript->setRadius(radius);
694    mRsScript->blur(ain, aout);
695
696    // replace the original image's pointer, avoiding a copy back to the original buffer
697    free(*image);
698    *image = outImage;
699}
700
701}; // namespace uirenderer
702}; // namespace android
703