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