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