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