FontRenderer.cpp revision f7648b7f24bc4cf8a66df4c0cfb5e8aa2b7d3ac8
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 "Rect.h"
39
40namespace android {
41namespace uirenderer {
42
43// blur inputs smaller than this constant will bypass renderscript
44#define RS_MIN_INPUT_CUTOFF 10000
45
46///////////////////////////////////////////////////////////////////////////////
47// FontRenderer
48///////////////////////////////////////////////////////////////////////////////
49
50static bool sLogFontRendererCreate = true;
51
52FontRenderer::FontRenderer() :
53        mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) {
54
55    if (sLogFontRendererCreate) {
56        INIT_LOGD("Creating FontRenderer");
57    }
58
59    mGammaTable = NULL;
60    mInitialized = false;
61    mMaxNumberOfQuads = 1024;
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#if DEBUG_FONT_RENDERER
140    uint16_t totalGlyphs = 0;
141    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
142        totalGlyphs += mCacheTextures[i]->getGlyphCount();
143        // Erase caches, just as a debugging facility
144        if (mCacheTextures[i]->getTexture()) {
145            memset(mCacheTextures[i]->getTexture(), 0,
146                    mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight());
147        }
148    }
149    ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs);
150#endif
151}
152
153void FontRenderer::flushLargeCaches() {
154    // Start from 1; don't deallocate smallest/default texture
155    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
156        CacheTexture* cacheTexture = mCacheTextures[i];
157        if (cacheTexture->getTexture()) {
158            cacheTexture->init();
159            LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
160            while (it.next()) {
161                it.value()->invalidateTextureCache(cacheTexture);
162            }
163            cacheTexture->releaseTexture();
164        }
165    }
166}
167
168CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
169        uint32_t* startX, uint32_t* startY) {
170    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
171        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
172            return mCacheTextures[i];
173        }
174    }
175    // Could not fit glyph into current cache textures
176    return NULL;
177}
178
179void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
180        uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
181    checkInit();
182
183    // If the glyph bitmap is empty let's assum the glyph is valid
184    // so we can avoid doing extra work later on
185    if (glyph.fWidth == 0 || glyph.fHeight == 0) {
186        cachedGlyph->mIsValid = true;
187        cachedGlyph->mCacheTexture = NULL;
188        return;
189    }
190
191    cachedGlyph->mIsValid = false;
192
193    // If the glyph is too tall, don't cache it
194    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
195                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
196        ALOGE("Font size too large to fit in cache. width, height = %i, %i",
197                (int) glyph.fWidth, (int) glyph.fHeight);
198        return;
199    }
200
201    // Now copy the bitmap into the cache texture
202    uint32_t startX = 0;
203    uint32_t startY = 0;
204
205    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
206
207    if (!cacheTexture) {
208        if (!precaching) {
209            // If the new glyph didn't fit and we are not just trying to precache it,
210            // clear out the cache and try again
211            flushAllAndInvalidate();
212            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
213        }
214
215        if (!cacheTexture) {
216            // either the glyph didn't fit or we're precaching and will cache it when we draw
217            return;
218        }
219    }
220
221    cachedGlyph->mCacheTexture = cacheTexture;
222
223    *retOriginX = startX;
224    *retOriginY = startY;
225
226    uint32_t endX = startX + glyph.fWidth;
227    uint32_t endY = startY + glyph.fHeight;
228
229    uint32_t cacheWidth = cacheTexture->getWidth();
230
231    if (!cacheTexture->getTexture()) {
232        Caches::getInstance().activeTexture(0);
233        // Large-glyph texture memory is allocated only as needed
234        cacheTexture->allocateTexture();
235    }
236    if (!cacheTexture->mesh()) {
237        cacheTexture->allocateMesh();
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, mMaxNumberOfQuads);
311
312    if (allocate) {
313        Caches::getInstance().activeTexture(0);
314        cacheTexture->allocateTexture();
315        cacheTexture->allocateMesh();
316    }
317
318    return cacheTexture;
319}
320
321void FontRenderer::initTextTexture() {
322    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
323        delete mCacheTextures[i];
324    }
325    mCacheTextures.clear();
326
327    mUploadTexture = false;
328    mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
329    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
330    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
331    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
332    mCurrentCacheTexture = mCacheTextures[0];
333}
334
335// Avoid having to reallocate memory and render quad by quad
336void FontRenderer::initVertexArrayBuffers() {
337    uint32_t numIndices = mMaxNumberOfQuads * 6;
338    uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
339    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
340
341    // Four verts, two triangles , six indices per quad
342    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
343        int i6 = i * 6;
344        int i4 = i * 4;
345
346        indexBufferData[i6 + 0] = i4 + 0;
347        indexBufferData[i6 + 1] = i4 + 1;
348        indexBufferData[i6 + 2] = i4 + 2;
349
350        indexBufferData[i6 + 3] = i4 + 0;
351        indexBufferData[i6 + 4] = i4 + 2;
352        indexBufferData[i6 + 5] = i4 + 3;
353    }
354
355    glGenBuffers(1, &mIndexBufferID);
356    Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
357    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
358
359    free(indexBufferData);
360}
361
362// We don't want to allocate anything unless we actually draw text
363void FontRenderer::checkInit() {
364    if (mInitialized) {
365        return;
366    }
367
368    initTextTexture();
369    initVertexArrayBuffers();
370
371    mInitialized = true;
372}
373
374void FontRenderer::checkTextureUpdate() {
375    if (!mUploadTexture) {
376        return;
377    }
378
379    Caches& caches = Caches::getInstance();
380    GLuint lastTextureId = 0;
381
382    // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
383    // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
384    // With OpenGL ES 2.0 we have to upload entire stripes instead.
385    const bool hasUnpackRowLength = Extensions::getInstance().getMajorGlVersion() >= 3;
386    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
387
388    // Iterate over all the cache textures and see which ones need to be updated
389    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
390        CacheTexture* cacheTexture = mCacheTextures[i];
391        if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
392            const Rect* dirtyRect = cacheTexture->getDirtyRect();
393            uint32_t x = hasUnpackRowLength ? dirtyRect->left : 0;
394            uint32_t y = dirtyRect->top;
395            uint32_t width = cacheTexture->getWidth();
396            uint32_t height = dirtyRect->getHeight();
397            void* textureData = cacheTexture->getTexture() + y * width + x;
398
399            if (cacheTexture->getTextureId() != lastTextureId) {
400                lastTextureId = cacheTexture->getTextureId();
401                caches.activeTexture(0);
402                glBindTexture(GL_TEXTURE_2D, lastTextureId);
403
404                // The unpack row length only needs to be specified when a new
405                // texture is bound
406                if (hasUnpackRowLength) {
407                    glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
408                }
409            }
410
411            // If we can upload a sub-rectangle, use the dirty rect width
412            // instead of the width of the entire texture
413            if (hasUnpackRowLength) {
414                width = dirtyRect->getWidth();
415            }
416
417#if DEBUG_FONT_RENDERER
418            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
419                    i, x, y, width, height);
420#endif
421
422            glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
423                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
424
425            cacheTexture->setDirty(false);
426        }
427    }
428
429    // Reset to default unpack row length to avoid affecting texture
430    // uploads in other parts of the renderer
431    if (hasUnpackRowLength) {
432        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
433    }
434
435    mUploadTexture = false;
436}
437
438void FontRenderer::issueDrawCommand() {
439    bool first = true;
440    bool force = false;
441
442    GLuint lastId = 0;
443    Caches& caches = Caches::getInstance();
444
445    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
446        CacheTexture* texture = mCacheTextures[i];
447        if (texture->canDraw()) {
448            if (first) {
449                if (mFunctor) (*mFunctor)(0, NULL);
450
451                checkTextureUpdate();
452                caches.bindIndicesBuffer(mIndexBufferID);
453
454                if (!mDrawn) {
455                    // If returns true, a VBO was bound and we must
456                    // rebind our vertex attrib pointers even if
457                    // they have the same values as the current pointers
458                    force = caches.unbindMeshBuffer();
459                }
460
461                caches.activeTexture(0);
462                first = false;
463            }
464
465            glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
466            texture->setLinearFiltering(mLinearFiltering, false);
467
468            TextureVertex* mesh = texture->mesh();
469            caches.bindPositionVertexPointer(force, &mesh[0].position[0]);
470            caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]);
471            force = false;
472
473            glDrawElements(GL_TRIANGLES, texture->meshElementCount(),
474                    GL_UNSIGNED_SHORT, texture->indices());
475
476            texture->resetMesh();
477        }
478    }
479
480    mDrawn = true;
481}
482
483void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
484        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
485        float x4, float y4, float u4, float v4, CacheTexture* texture) {
486    if (texture != mCurrentCacheTexture) {
487        // Now use the new texture id
488        mCurrentCacheTexture = texture;
489    }
490
491    mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2,
492            x3, y3, u3, v3, x4, y4, u4, v4);
493}
494
495void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
496        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
497        float x4, float y4, float u4, float v4, CacheTexture* texture) {
498
499    if (mClip &&
500            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
501        return;
502    }
503
504    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
505
506    if (mBounds) {
507        mBounds->left = fmin(mBounds->left, x1);
508        mBounds->top = fmin(mBounds->top, y3);
509        mBounds->right = fmax(mBounds->right, x3);
510        mBounds->bottom = fmax(mBounds->bottom, y1);
511    }
512
513    if (mCurrentCacheTexture->endOfMesh()) {
514        issueDrawCommand();
515    }
516}
517
518void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
519        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
520        float x4, float y4, float u4, float v4, CacheTexture* texture) {
521
522    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
523
524    if (mBounds) {
525        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
526        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
527        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
528        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
529    }
530
531    if (mCurrentCacheTexture->endOfMesh()) {
532        issueDrawCommand();
533    }
534}
535
536void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
537    mCurrentFont = Font::create(this, paint, matrix);
538}
539
540FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
541        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
542    checkInit();
543
544    if (!mCurrentFont) {
545        DropShadow image;
546        image.width = 0;
547        image.height = 0;
548        image.image = NULL;
549        image.penX = 0;
550        image.penY = 0;
551        return image;
552    }
553
554    mDrawn = false;
555    mClip = NULL;
556    mBounds = NULL;
557
558    Rect bounds;
559    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
560
561    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
562    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
563
564#ifdef ANDROID_ENABLE_RENDERSCRIPT
565    // Align buffers for renderscript usage
566    if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) {
567        paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT;
568    }
569    int size = paddedWidth * paddedHeight;
570    uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size);
571#else
572    int size = paddedWidth * paddedHeight;
573    uint8_t* dataBuffer = (uint8_t*) malloc(size);
574#endif
575
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, Functor* functor) {
602    checkInit();
603
604    mDrawn = false;
605    mBounds = bounds;
606    mFunctor = functor;
607    mClip = clip;
608}
609
610void FontRenderer::finishRender() {
611    mBounds = NULL;
612    mClip = NULL;
613
614    issueDrawCommand();
615}
616
617void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
618    Font* font = Font::create(this, paint, matrix);
619    font->precache(paint, text, numGlyphs);
620}
621
622bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
623        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
624        const float* positions, Rect* bounds, Functor* functor) {
625    if (!mCurrentFont) {
626        ALOGE("No font set");
627        return false;
628    }
629
630    initRender(clip, bounds, functor);
631    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
632    finishRender();
633
634    return mDrawn;
635}
636
637bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
638        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
639        float hOffset, float vOffset, Rect* bounds) {
640    if (!mCurrentFont) {
641        ALOGE("No font set");
642        return false;
643    }
644
645    initRender(clip, bounds, NULL);
646    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
647    finishRender();
648
649    return mDrawn;
650}
651
652void FontRenderer::removeFont(const Font* font) {
653    mActiveFonts.remove(font->getDescription());
654
655    if (mCurrentFont == font) {
656        mCurrentFont = NULL;
657    }
658}
659
660void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
661#ifdef ANDROID_ENABLE_RENDERSCRIPT
662    if (width * height * radius >= RS_MIN_INPUT_CUTOFF) {
663        uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
664
665        if (mRs.get() == 0) {
666            mRs = new RSC::RS();
667            if (!mRs->init(true, true)) {
668                ALOGE("blur RS failed to init");
669            }
670
671            mRsElement = RSC::Element::A_8(mRs);
672            mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
673        }
674
675        sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
676        sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
677                RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image);
678        sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
679                RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage);
680
681        mRsScript->setRadius(radius);
682        mRsScript->blur(ain, aout);
683
684        // replace the original image's pointer, avoiding a copy back to the original buffer
685        free(*image);
686        *image = outImage;
687
688        return;
689    }
690#endif
691
692    float *gaussian = new float[2 * radius + 1];
693    Blur::generateGaussianWeights(gaussian, radius);
694
695    uint8_t* scratch = new uint8_t[width * height];
696    Blur::horizontal(gaussian, radius, *image, scratch, width, height);
697    Blur::vertical(gaussian, radius, scratch, *image, width, height);
698
699    delete[] gaussian;
700    delete[] scratch;
701}
702
703}; // namespace uirenderer
704}; // namespace android
705