FontRenderer.cpp revision e4d9a01bfc7451afff1ed399a5801c7aa2af2831
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 "FontRenderer.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 mMaxNumberOfQuads = 1024; 61 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 119 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 120 while (it.next()) { 121 delete it.value(); 122 } 123 mActiveFonts.clear(); 124} 125 126void FontRenderer::flushAllAndInvalidate() { 127 issueDrawCommand(); 128 129 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 130 while (it.next()) { 131 it.value()->invalidateTextureCache(); 132 } 133 134 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 135 mCacheTextures[i]->init(); 136 } 137 138#if DEBUG_FONT_RENDERER 139 uint16_t totalGlyphs = 0; 140 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 141 totalGlyphs += mCacheTextures[i]->getGlyphCount(); 142 // Erase caches, just as a debugging facility 143 if (mCacheTextures[i]->getTexture()) { 144 memset(mCacheTextures[i]->getTexture(), 0, 145 mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight()); 146 } 147 } 148 ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); 149#endif 150} 151 152void FontRenderer::flushLargeCaches() { 153 // Start from 1; don't deallocate smallest/default texture 154 for (uint32_t i = 1; i < mCacheTextures.size(); i++) { 155 CacheTexture* cacheTexture = mCacheTextures[i]; 156 if (cacheTexture->getTexture()) { 157 cacheTexture->init(); 158 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 159 while (it.next()) { 160 it.value()->invalidateTextureCache(cacheTexture); 161 } 162 cacheTexture->releaseTexture(); 163 } 164 } 165} 166 167CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, 168 uint32_t* startX, uint32_t* startY) { 169 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 170 if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { 171 return mCacheTextures[i]; 172 } 173 } 174 // Could not fit glyph into current cache textures 175 return NULL; 176} 177 178void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, 179 uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { 180 checkInit(); 181 182 // If the glyph bitmap is empty let's assum the glyph is valid 183 // so we can avoid doing extra work later on 184 if (glyph.fWidth == 0 || glyph.fHeight == 0) { 185 cachedGlyph->mIsValid = true; 186 cachedGlyph->mCacheTexture = NULL; 187 return; 188 } 189 190 cachedGlyph->mIsValid = false; 191 192 // If the glyph is too tall, don't cache it 193 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > 194 mCacheTextures[mCacheTextures.size() - 1]->getHeight()) { 195 ALOGE("Font size too large to fit in cache. width, height = %i, %i", 196 (int) glyph.fWidth, (int) glyph.fHeight); 197 return; 198 } 199 200 // Now copy the bitmap into the cache texture 201 uint32_t startX = 0; 202 uint32_t startY = 0; 203 204 CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); 205 206 if (!cacheTexture) { 207 if (!precaching) { 208 // If the new glyph didn't fit and we are not just trying to precache it, 209 // clear out the cache and try again 210 flushAllAndInvalidate(); 211 cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); 212 } 213 214 if (!cacheTexture) { 215 // either the glyph didn't fit or we're precaching and will cache it when we draw 216 return; 217 } 218 } 219 220 cachedGlyph->mCacheTexture = cacheTexture; 221 222 *retOriginX = startX; 223 *retOriginY = startY; 224 225 uint32_t endX = startX + glyph.fWidth; 226 uint32_t endY = startY + glyph.fHeight; 227 228 uint32_t cacheWidth = cacheTexture->getWidth(); 229 230 if (!cacheTexture->getTexture()) { 231 Caches::getInstance().activeTexture(0); 232 // Large-glyph texture memory is allocated only as needed 233 cacheTexture->allocateTexture(); 234 } 235 if (!cacheTexture->mesh()) { 236 cacheTexture->allocateMesh(); 237 } 238 239 // Tells us whether the glyphs is B&W (1 bit per pixel) 240 // or anti-aliased (8 bits per pixel) 241 SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); 242 243 uint8_t* cacheBuffer = cacheTexture->getTexture(); 244 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 245 246 // Copy the glyph image, taking the mask format into account 247 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 248 int stride = glyph.rowBytes(); 249 250 uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; 251 memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); 252 253 switch (format) { 254 case SkMask::kA8_Format: { 255 if (mGammaTable) { 256 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { 257 row = cacheY * cacheWidth; 258 cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; 259 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 260 uint8_t tempCol = bitmapBuffer[bY + bX]; 261 cacheBuffer[row + cacheX] = mGammaTable[tempCol]; 262 } 263 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; 264 } 265 } else { 266 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { 267 row = cacheY * cacheWidth; 268 memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); 269 cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; 270 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; 271 } 272 } 273 break; 274 } 275 case SkMask::kBW_Format: { 276 static const uint8_t COLORS[2] = { 0, 255 }; 277 278 for (cacheY = startY; cacheY < endY; cacheY++) { 279 cacheX = startX; 280 int rowBytes = stride; 281 uint8_t* buffer = bitmapBuffer; 282 283 row = cacheY * cacheWidth; 284 cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; 285 while (--rowBytes >= 0) { 286 uint8_t b = *buffer++; 287 for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) { 288 cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1]; 289 } 290 } 291 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; 292 293 bitmapBuffer += stride; 294 } 295 break; 296 } 297 default: 298 ALOGW("Unkown glyph format: 0x%x", format); 299 break; 300 } 301 302 row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; 303 memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); 304 305 cachedGlyph->mIsValid = true; 306} 307 308CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { 309 CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads); 310 311 if (allocate) { 312 Caches::getInstance().activeTexture(0); 313 cacheTexture->allocateTexture(); 314 cacheTexture->allocateMesh(); 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 361// We don't want to allocate anything unless we actually draw text 362void FontRenderer::checkInit() { 363 if (mInitialized) { 364 return; 365 } 366 367 initTextTexture(); 368 initVertexArrayBuffers(); 369 370 mInitialized = true; 371} 372 373void FontRenderer::checkTextureUpdate() { 374 if (!mUploadTexture) { 375 return; 376 } 377 378 Caches& caches = Caches::getInstance(); 379 GLuint lastTextureId = 0; 380 // Iterate over all the cache textures and see which ones need to be updated 381 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 382 CacheTexture* cacheTexture = mCacheTextures[i]; 383 if (cacheTexture->isDirty() && cacheTexture->getTexture()) { 384 // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer 385 // of data. So expand the dirty rect to the encompassing horizontal stripe. 386 const Rect* dirtyRect = cacheTexture->getDirtyRect(); 387 uint32_t x = 0; 388 uint32_t y = dirtyRect->top; 389 uint32_t width = cacheTexture->getWidth(); 390 uint32_t height = dirtyRect->getHeight(); 391 void* textureData = cacheTexture->getTexture() + y * width; 392 393 if (cacheTexture->getTextureId() != lastTextureId) { 394 lastTextureId = cacheTexture->getTextureId(); 395 caches.activeTexture(0); 396 glBindTexture(GL_TEXTURE_2D, lastTextureId); 397 } 398#if DEBUG_FONT_RENDERER 399 ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", 400 i, x, y, width, height); 401#endif 402 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, 403 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 404 cacheTexture->setDirty(false); 405 } 406 } 407 408 mUploadTexture = false; 409} 410 411void FontRenderer::issueDrawCommand() { 412 bool first = true; 413 bool force = false; 414 415 GLuint lastId = 0; 416 Caches& caches = Caches::getInstance(); 417 418 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 419 CacheTexture* texture = mCacheTextures[i]; 420 if (texture->canDraw()) { 421 if (first) { 422 if (mFunctor) (*mFunctor)(0, NULL); 423 424 checkTextureUpdate(); 425 caches.bindIndicesBuffer(mIndexBufferID); 426 427 if (!mDrawn) { 428 // If returns true, a VBO was bound and we must 429 // rebind our vertex attrib pointers even if 430 // they have the same values as the current pointers 431 force = caches.unbindMeshBuffer(); 432 } 433 434 caches.activeTexture(0); 435 first = false; 436 } 437 438 glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); 439 texture->setLinearFiltering(mLinearFiltering, false); 440 441 TextureVertex* mesh = texture->mesh(); 442 caches.bindPositionVertexPointer(force, &mesh[0].position[0]); 443 caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]); 444 force = false; 445 446 glDrawElements(GL_TRIANGLES, texture->meshElementCount(), 447 GL_UNSIGNED_SHORT, texture->indices()); 448 449 texture->resetMesh(); 450 } 451 } 452 453 mDrawn = true; 454} 455 456void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, 457 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 458 float x4, float y4, float u4, float v4, CacheTexture* texture) { 459 if (texture != mCurrentCacheTexture) { 460 // Now use the new texture id 461 mCurrentCacheTexture = texture; 462 } 463 464 mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2, 465 x3, y3, u3, v3, x4, y4, u4, v4); 466} 467 468void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, 469 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 470 float x4, float y4, float u4, float v4, CacheTexture* texture) { 471 472 if (mClip && 473 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { 474 return; 475 } 476 477 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 478 479 if (mBounds) { 480 mBounds->left = fmin(mBounds->left, x1); 481 mBounds->top = fmin(mBounds->top, y3); 482 mBounds->right = fmax(mBounds->right, x3); 483 mBounds->bottom = fmax(mBounds->bottom, y1); 484 } 485 486 if (mCurrentCacheTexture->endOfMesh()) { 487 issueDrawCommand(); 488 } 489} 490 491void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, 492 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 493 float x4, float y4, float u4, float v4, CacheTexture* texture) { 494 495 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 496 497 if (mBounds) { 498 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4)))); 499 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); 500 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); 501 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); 502 } 503 504 if (mCurrentCacheTexture->endOfMesh()) { 505 issueDrawCommand(); 506 } 507} 508 509void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) { 510 mCurrentFont = Font::create(this, paint, matrix); 511} 512 513FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, 514 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) { 515 checkInit(); 516 517 if (!mCurrentFont) { 518 DropShadow image; 519 image.width = 0; 520 image.height = 0; 521 image.image = NULL; 522 image.penX = 0; 523 image.penY = 0; 524 return image; 525 } 526 527 mDrawn = false; 528 mClip = NULL; 529 mBounds = NULL; 530 531 Rect bounds; 532 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); 533 534 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; 535 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; 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 blurImage(&dataBuffer, paddedWidth, paddedHeight, radius); 562 } 563 564 DropShadow image; 565 image.width = paddedWidth; 566 image.height = paddedHeight; 567 image.image = dataBuffer; 568 image.penX = penX; 569 image.penY = penY; 570 571 return image; 572} 573 574void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) { 575 checkInit(); 576 577 mDrawn = false; 578 mBounds = bounds; 579 mFunctor = functor; 580 mClip = clip; 581} 582 583void FontRenderer::finishRender() { 584 mBounds = NULL; 585 mClip = NULL; 586 587 issueDrawCommand(); 588} 589 590void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) { 591 Font* font = Font::create(this, paint, matrix); 592 font->precache(paint, text, numGlyphs); 593} 594 595bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, 596 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, 597 const float* positions, Rect* bounds, Functor* functor) { 598 if (!mCurrentFont) { 599 ALOGE("No font set"); 600 return false; 601 } 602 603 initRender(clip, bounds, functor); 604 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); 605 finishRender(); 606 607 return mDrawn; 608} 609 610bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, 611 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, 612 float hOffset, float vOffset, Rect* bounds) { 613 if (!mCurrentFont) { 614 ALOGE("No font set"); 615 return false; 616 } 617 618 initRender(clip, bounds, NULL); 619 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); 620 finishRender(); 621 622 return mDrawn; 623} 624 625void FontRenderer::removeFont(const Font* font) { 626 mActiveFonts.remove(font->getDescription()); 627 628 if (mCurrentFont == font) { 629 mCurrentFont = NULL; 630 } 631} 632 633void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) { 634#ifdef ANDROID_ENABLE_RENDERSCRIPT 635 if (width * height * radius >= RS_MIN_INPUT_CUTOFF) { 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 return; 662 } 663#endif 664 665 float *gaussian = new float[2 * radius + 1]; 666 Blur::generateGaussianWeights(gaussian, radius); 667 668 uint8_t* scratch = new uint8_t[width * height]; 669 Blur::horizontal(gaussian, radius, *image, scratch, width, height); 670 Blur::vertical(gaussian, radius, scratch, *image, width, height); 671 672 delete[] gaussian; 673 delete[] scratch; 674} 675 676}; // namespace uirenderer 677}; // namespace android 678