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