FontRenderer.cpp revision 6e2004089305cf2cd958b52b234459a49a4e5c83
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        mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6));
382        mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex);
383        mDrawCacheTextures.add(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    for (uint32_t i = 0; i < mDrawOffsets.size(); i++) {
442        uint16_t* offset = mDrawOffsets[i];
443        uint32_t count = mDrawCounts[i];
444        CacheTexture* texture = mDrawCacheTextures[i];
445
446        caches.activeTexture(0);
447        glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
448
449        texture->setLinearFiltering(mLinearFiltering, false);
450
451        glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset);
452    }
453
454    mDrawn = true;
455
456    mCurrentQuadIndex = 0;
457    mLastQuadIndex = 0;
458    mDrawOffsets.clear();
459    mDrawCounts.clear();
460    mDrawCacheTextures.clear();
461}
462
463void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
464        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
465        float x4, float y4, float u4, float v4, CacheTexture* texture) {
466    if (texture != mCurrentCacheTexture) {
467        updateDrawParams();
468        // Now use the new texture id
469        mCurrentCacheTexture = texture;
470    }
471
472    const uint32_t vertsPerQuad = 4;
473    const uint32_t floatsPerVert = 4;
474    float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
475
476    (*currentPos++) = x1;
477    (*currentPos++) = y1;
478    (*currentPos++) = u1;
479    (*currentPos++) = v1;
480
481    (*currentPos++) = x2;
482    (*currentPos++) = y2;
483    (*currentPos++) = u2;
484    (*currentPos++) = v2;
485
486    (*currentPos++) = x3;
487    (*currentPos++) = y3;
488    (*currentPos++) = u3;
489    (*currentPos++) = v3;
490
491    (*currentPos++) = x4;
492    (*currentPos++) = y4;
493    (*currentPos++) = u4;
494    (*currentPos++) = v4;
495
496    mCurrentQuadIndex++;
497}
498
499void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
500        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
501        float x4, float y4, float u4, float v4, CacheTexture* texture) {
502
503    if (mClip &&
504            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
505        return;
506    }
507
508    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
509
510    if (mBounds) {
511        mBounds->left = fmin(mBounds->left, x1);
512        mBounds->top = fmin(mBounds->top, y3);
513        mBounds->right = fmax(mBounds->right, x3);
514        mBounds->bottom = fmax(mBounds->bottom, y1);
515    }
516
517    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
518        issueDrawCommand();
519    }
520}
521
522void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
523        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
524        float x4, float y4, float u4, float v4, CacheTexture* texture) {
525
526    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
527
528    if (mBounds) {
529        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
530        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
531        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
532        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
533    }
534
535    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
536        issueDrawCommand();
537    }
538}
539
540void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
541    mCurrentFont = Font::create(this, paint, matrix);
542}
543
544FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
545        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
546    checkInit();
547
548    if (!mCurrentFont) {
549        DropShadow image;
550        image.width = 0;
551        image.height = 0;
552        image.image = NULL;
553        image.penX = 0;
554        image.penY = 0;
555        return image;
556    }
557
558    mDrawn = false;
559    mClip = NULL;
560    mBounds = NULL;
561
562    Rect bounds;
563    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
564
565    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
566    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
567
568    // Align buffers for renderscript usage
569    if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) {
570        paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT;
571    }
572
573    int size = paddedWidth * paddedHeight;
574    uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size);
575    memset(dataBuffer, 0, size);
576
577    int penX = radius - bounds.left;
578    int penY = radius - bounds.bottom;
579
580    if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
581        // text has non-whitespace, so draw and blur to create the shadow
582        // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
583        // TODO: don't draw pure whitespace in the first place, and avoid needing this check
584        mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
585                Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
586
587        blurImage(&dataBuffer, paddedWidth, paddedHeight, radius);
588    }
589
590    DropShadow image;
591    image.width = paddedWidth;
592    image.height = paddedHeight;
593    image.image = dataBuffer;
594    image.penX = penX;
595    image.penY = penY;
596
597    return image;
598}
599
600void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
601    checkInit();
602
603    mDrawn = false;
604    mBounds = bounds;
605    mClip = clip;
606}
607
608void FontRenderer::finishRender() {
609    mBounds = NULL;
610    mClip = NULL;
611
612    if (mCurrentQuadIndex != 0) {
613        issueDrawCommand();
614    }
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) {
625    if (!mCurrentFont) {
626        ALOGE("No font set");
627        return false;
628    }
629
630    initRender(clip, bounds);
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);
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    if (width * height * radius < RS_MIN_INPUT_CUTOFF) {
662        float *gaussian = new float[2 * radius + 1];
663        Blur::generateGaussianWeights(gaussian, radius);
664
665        uint8_t* scratch = new uint8_t[width * height];
666        Blur::horizontal(gaussian, radius, *image, scratch, width, height);
667        Blur::vertical(gaussian, radius, scratch, *image, width, height);
668
669        delete[] gaussian;
670        delete[] scratch;
671        return;
672    }
673
674    uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
675
676    if (mRs.get() == 0) {
677        mRs = new RSC::RS();
678        if (!mRs->init(true, true)) {
679            ALOGE("blur RS failed to init");
680        }
681
682        mRsElement = RSC::Element::A_8(mRs);
683        mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
684    }
685
686    sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
687    sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
688            RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image);
689    sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
690            RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage);
691
692    mRsScript->setRadius(radius);
693    mRsScript->blur(ain, aout);
694
695    // replace the original image's pointer, avoiding a copy back to the original buffer
696    free(*image);
697    *image = outImage;
698}
699
700}; // namespace uirenderer
701}; // namespace android
702