FontRenderer.cpp revision bf5703e52e3304246cbf0e73f6976f7d7312d238
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 "Caches.h" 27#include "Debug.h" 28#include "FontRenderer.h" 29#include "Rect.h" 30 31namespace android { 32namespace uirenderer { 33 34/////////////////////////////////////////////////////////////////////////////// 35// FontRenderer 36/////////////////////////////////////////////////////////////////////////////// 37 38static bool sLogFontRendererCreate = true; 39 40FontRenderer::FontRenderer() : 41 mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) { 42 43 if (sLogFontRendererCreate) { 44 INIT_LOGD("Creating FontRenderer"); 45 } 46 47 mGammaTable = NULL; 48 mInitialized = false; 49 mMaxNumberOfQuads = 1024; 50 mCurrentQuadIndex = 0; 51 mLastQuadIndex = 0; 52 53 mTextMesh = NULL; 54 mCurrentCacheTexture = NULL; 55 56 mLinearFiltering = false; 57 58 mIndexBufferID = 0; 59 60 mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH; 61 mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT; 62 mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH; 63 mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT; 64 65 char property[PROPERTY_VALUE_MAX]; 66 if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) { 67 mSmallCacheWidth = atoi(property); 68 } 69 70 if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) { 71 mSmallCacheHeight = atoi(property); 72 } 73 74 if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) { 75 mLargeCacheWidth = atoi(property); 76 } 77 78 if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) { 79 mLargeCacheHeight = atoi(property); 80 } 81 82 uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize; 83 mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth; 84 mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight; 85 mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth; 86 mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight; 87 88 if (sLogFontRendererCreate) { 89 INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", 90 mSmallCacheWidth, mSmallCacheHeight, 91 mLargeCacheWidth, mLargeCacheHeight >> 1, 92 mLargeCacheWidth, mLargeCacheHeight >> 1, 93 mLargeCacheWidth, mLargeCacheHeight); 94 } 95 96 sLogFontRendererCreate = false; 97} 98 99FontRenderer::~FontRenderer() { 100 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 101 delete mCacheTextures[i]; 102 } 103 mCacheTextures.clear(); 104 105 if (mInitialized) { 106 // Unbinding the buffer shouldn't be necessary but it crashes with some drivers 107 Caches::getInstance().unbindIndicesBuffer(); 108 glDeleteBuffers(1, &mIndexBufferID); 109 110 delete[] mTextMesh; 111 } 112 113 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 114 while (it.next()) { 115 delete it.value(); 116 } 117 mActiveFonts.clear(); 118} 119 120void FontRenderer::flushAllAndInvalidate() { 121 if (mCurrentQuadIndex != 0) { 122 issueDrawCommand(); 123 } 124 125 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 126 while (it.next()) { 127 it.value()->invalidateTextureCache(); 128 } 129 130 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 131 mCacheTextures[i]->init(); 132 } 133 134#if DEBUG_FONT_RENDERER 135 uint16_t totalGlyphs = 0; 136 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 137 totalGlyphs += mCacheTextures[i]->getGlyphCount(); 138 // Erase caches, just as a debugging facility 139 if (mCacheTextures[i]->getTexture()) { 140 memset(mCacheTextures[i]->getTexture(), 0, 141 mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight()); 142 } 143 } 144 ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); 145#endif 146} 147 148void FontRenderer::flushLargeCaches() { 149 // Start from 1; don't deallocate smallest/default texture 150 for (uint32_t i = 1; i < mCacheTextures.size(); i++) { 151 CacheTexture* cacheTexture = mCacheTextures[i]; 152 if (cacheTexture->getTexture()) { 153 cacheTexture->init(); 154 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 155 while (it.next()) { 156 it.value()->invalidateTextureCache(cacheTexture); 157 } 158 cacheTexture->releaseTexture(); 159 } 160 } 161} 162 163CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, 164 uint32_t* startX, uint32_t* startY) { 165 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 166 if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { 167 return mCacheTextures[i]; 168 } 169 } 170 // Could not fit glyph into current cache textures 171 return NULL; 172} 173 174void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, 175 uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { 176 checkInit(); 177 cachedGlyph->mIsValid = false; 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->getTexture()) { 217 Caches::getInstance().activeTexture(0); 218 // Large-glyph texture memory is allocated only as needed 219 cacheTexture->allocateTexture(); 220 } 221 222 // Tells us whether the glyphs is B&W (1 bit per pixel) 223 // or anti-aliased (8 bits per pixel) 224 SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); 225 226 uint8_t* cacheBuffer = cacheTexture->getTexture(); 227 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 228 229 // Zero out the borders 230 for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) { 231 cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0; 232 cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0; 233 } 234 235 for (cacheY = startY - TEXTURE_BORDER_SIZE + 1; 236 cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) { 237 cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0; 238 cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0; 239 } 240 241 // Copy the glyph image, taking the mask format into account 242 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 243 int stride = glyph.rowBytes(); 244 245 switch (format) { 246 case SkMask::kA8_Format: { 247 if (mGammaTable) { 248 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { 249 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 250 uint8_t tempCol = bitmapBuffer[bY + bX]; 251 cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol]; 252 } 253 } 254 } else { 255 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { 256 memcpy(&cacheBuffer[cacheY * cacheWidth + startX], &bitmapBuffer[bY], 257 glyph.fWidth); 258 } 259 } 260 break; 261 } 262 case SkMask::kBW_Format: { 263 static const uint8_t COLORS[2] = { 0, 255 }; 264 265 for (cacheY = startY; cacheY < endY; cacheY++) { 266 cacheX = startX; 267 int rowBytes = stride; 268 uint8_t* buffer = bitmapBuffer; 269 270 while (--rowBytes >= 0) { 271 uint8_t b = *buffer++; 272 for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) { 273 cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1]; 274 } 275 } 276 277 bitmapBuffer += stride; 278 } 279 break; 280 } 281 default: 282 ALOGW("Unkown glyph format: 0x%x", format); 283 break; 284 } 285 286 cachedGlyph->mIsValid = true; 287} 288 289CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { 290 CacheTexture* cacheTexture = new CacheTexture(width, height); 291 292 if (allocate) { 293 Caches::getInstance().activeTexture(0); 294 cacheTexture->allocateTexture(); 295 } 296 297 return cacheTexture; 298} 299 300void FontRenderer::initTextTexture() { 301 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 302 delete mCacheTextures[i]; 303 } 304 mCacheTextures.clear(); 305 306 mUploadTexture = false; 307 mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); 308 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); 309 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); 310 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false)); 311 mCurrentCacheTexture = mCacheTextures[0]; 312} 313 314// Avoid having to reallocate memory and render quad by quad 315void FontRenderer::initVertexArrayBuffers() { 316 uint32_t numIndices = mMaxNumberOfQuads * 6; 317 uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); 318 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); 319 320 // Four verts, two triangles , six indices per quad 321 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) { 322 int i6 = i * 6; 323 int i4 = i * 4; 324 325 indexBufferData[i6 + 0] = i4 + 0; 326 indexBufferData[i6 + 1] = i4 + 1; 327 indexBufferData[i6 + 2] = i4 + 2; 328 329 indexBufferData[i6 + 3] = i4 + 0; 330 indexBufferData[i6 + 4] = i4 + 2; 331 indexBufferData[i6 + 5] = i4 + 3; 332 } 333 334 glGenBuffers(1, &mIndexBufferID); 335 Caches::getInstance().bindIndicesBuffer(mIndexBufferID); 336 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); 337 338 free(indexBufferData); 339 340 uint32_t coordSize = 2; 341 uint32_t uvSize = 2; 342 uint32_t vertsPerQuad = 4; 343 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize; 344 mTextMesh = new float[vertexBufferSize]; 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::updateDrawParams() { 360 if (mCurrentQuadIndex != mLastQuadIndex) { 361 mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6)); 362 mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex); 363 mDrawCacheTextures.add(mCurrentCacheTexture); 364 mLastQuadIndex = mCurrentQuadIndex; 365 } 366} 367 368void FontRenderer::checkTextureUpdate() { 369 if (!mUploadTexture) { 370 return; 371 } 372 373 Caches& caches = Caches::getInstance(); 374 GLuint lastTextureId = 0; 375 // Iterate over all the cache textures and see which ones need to be updated 376 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 377 CacheTexture* cacheTexture = mCacheTextures[i]; 378 if (cacheTexture->isDirty() && cacheTexture->getTexture()) { 379 // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer 380 // of data. So expand the dirty rect to the encompassing horizontal stripe. 381 const Rect* dirtyRect = cacheTexture->getDirtyRect(); 382 uint32_t x = 0; 383 uint32_t y = dirtyRect->top; 384 uint32_t width = cacheTexture->getWidth(); 385 uint32_t height = dirtyRect->getHeight(); 386 void* textureData = cacheTexture->getTexture() + y * width; 387 388 if (cacheTexture->getTextureId() != lastTextureId) { 389 lastTextureId = cacheTexture->getTextureId(); 390 caches.activeTexture(0); 391 glBindTexture(GL_TEXTURE_2D, lastTextureId); 392 } 393#if DEBUG_FONT_RENDERER 394 ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", 395 i, x, y, width, height); 396#endif 397 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, 398 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 399 cacheTexture->setDirty(false); 400 } 401 } 402 403 mUploadTexture = false; 404} 405 406void FontRenderer::issueDrawCommand() { 407 updateDrawParams(); 408 checkTextureUpdate(); 409 410 Caches& caches = Caches::getInstance(); 411 caches.bindIndicesBuffer(mIndexBufferID); 412 if (!mDrawn) { 413 float* buffer = mTextMesh; 414 int offset = 2; 415 416 bool force = caches.unbindMeshBuffer(); 417 caches.bindPositionVertexPointer(force, buffer); 418 caches.bindTexCoordsVertexPointer(force, buffer + offset); 419 } 420 421 for (uint32_t i = 0; i < mDrawOffsets.size(); i++) { 422 uint16_t* offset = mDrawOffsets[i]; 423 uint32_t count = mDrawCounts[i]; 424 CacheTexture* texture = mDrawCacheTextures[i]; 425 426 caches.activeTexture(0); 427 glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); 428 429 texture->setLinearFiltering(mLinearFiltering, false); 430 431 glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset); 432 } 433 434 mDrawn = true; 435 436 mCurrentQuadIndex = 0; 437 mLastQuadIndex = 0; 438 mDrawOffsets.clear(); 439 mDrawCounts.clear(); 440 mDrawCacheTextures.clear(); 441} 442 443void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, 444 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 445 float x4, float y4, float u4, float v4, CacheTexture* texture) { 446 if (texture != mCurrentCacheTexture) { 447 updateDrawParams(); 448 // Now use the new texture id 449 mCurrentCacheTexture = texture; 450 } 451 452 const uint32_t vertsPerQuad = 4; 453 const uint32_t floatsPerVert = 4; 454 float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert; 455 456 (*currentPos++) = x1; 457 (*currentPos++) = y1; 458 (*currentPos++) = u1; 459 (*currentPos++) = v1; 460 461 (*currentPos++) = x2; 462 (*currentPos++) = y2; 463 (*currentPos++) = u2; 464 (*currentPos++) = v2; 465 466 (*currentPos++) = x3; 467 (*currentPos++) = y3; 468 (*currentPos++) = u3; 469 (*currentPos++) = v3; 470 471 (*currentPos++) = x4; 472 (*currentPos++) = y4; 473 (*currentPos++) = u4; 474 (*currentPos++) = v4; 475 476 mCurrentQuadIndex++; 477} 478 479void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, 480 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 481 float x4, float y4, float u4, float v4, CacheTexture* texture) { 482 483 if (mClip && 484 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { 485 return; 486 } 487 488 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 489 490 if (mBounds) { 491 mBounds->left = fmin(mBounds->left, x1); 492 mBounds->top = fmin(mBounds->top, y3); 493 mBounds->right = fmax(mBounds->right, x3); 494 mBounds->bottom = fmax(mBounds->bottom, y1); 495 } 496 497 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 498 issueDrawCommand(); 499 } 500} 501 502void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, 503 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 504 float x4, float y4, float u4, float v4, CacheTexture* texture) { 505 506 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 507 508 if (mBounds) { 509 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4)))); 510 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); 511 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); 512 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); 513 } 514 515 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 516 issueDrawCommand(); 517 } 518} 519 520void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) { 521 mCurrentFont = Font::create(this, paint, matrix); 522} 523 524FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, 525 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) { 526 checkInit(); 527 528 if (!mCurrentFont) { 529 DropShadow image; 530 image.width = 0; 531 image.height = 0; 532 image.image = NULL; 533 image.penX = 0; 534 image.penY = 0; 535 return image; 536 } 537 538 mDrawn = false; 539 mClip = NULL; 540 mBounds = NULL; 541 542 Rect bounds; 543 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); 544 545 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; 546 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; 547 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight]; 548 549 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) { 550 dataBuffer[i] = 0; 551 } 552 553 int penX = radius - bounds.left; 554 int penY = radius - bounds.bottom; 555 556 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, 557 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions); 558 blurImage(dataBuffer, paddedWidth, paddedHeight, radius); 559 560 DropShadow image; 561 image.width = paddedWidth; 562 image.height = paddedHeight; 563 image.image = dataBuffer; 564 image.penX = penX; 565 image.penY = penY; 566 567 return image; 568} 569 570void FontRenderer::initRender(const Rect* clip, Rect* bounds) { 571 checkInit(); 572 573 mDrawn = false; 574 mBounds = bounds; 575 mClip = clip; 576} 577 578void FontRenderer::finishRender() { 579 mBounds = NULL; 580 mClip = NULL; 581 582 if (mCurrentQuadIndex != 0) { 583 issueDrawCommand(); 584 } 585} 586 587void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) { 588 Font* font = Font::create(this, paint, matrix); 589 font->precache(paint, text, numGlyphs); 590} 591 592bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, 593 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, 594 const float* positions, Rect* bounds) { 595 if (!mCurrentFont) { 596 ALOGE("No font set"); 597 return false; 598 } 599 600 initRender(clip, bounds); 601 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); 602 finishRender(); 603 604 return mDrawn; 605} 606 607bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, 608 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, 609 float hOffset, float vOffset, Rect* bounds) { 610 if (!mCurrentFont) { 611 ALOGE("No font set"); 612 return false; 613 } 614 615 initRender(clip, bounds); 616 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); 617 finishRender(); 618 619 return mDrawn; 620} 621 622void FontRenderer::removeFont(const Font* font) { 623 mActiveFonts.remove(font->getDescription()); 624 625 if (mCurrentFont == font) { 626 mCurrentFont = NULL; 627 } 628} 629 630void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) { 631 // Compute gaussian weights for the blur 632 // e is the euler's number 633 float e = 2.718281828459045f; 634 float pi = 3.1415926535897932f; 635 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 ) 636 // x is of the form [-radius .. 0 .. radius] 637 // and sigma varies with radius. 638 // Based on some experimental radius values and sigma's 639 // we approximately fit sigma = f(radius) as 640 // sigma = radius * 0.3 + 0.6 641 // The larger the radius gets, the more our gaussian blur 642 // will resemble a box blur since with large sigma 643 // the gaussian curve begins to lose its shape 644 float sigma = 0.3f * (float) radius + 0.6f; 645 646 // Now compute the coefficints 647 // We will store some redundant values to save some math during 648 // the blur calculations 649 // precompute some values 650 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma); 651 float coeff2 = - 1.0f / (2.0f * sigma * sigma); 652 653 float normalizeFactor = 0.0f; 654 for (int32_t r = -radius; r <= radius; r ++) { 655 float floatR = (float) r; 656 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2); 657 normalizeFactor += weights[r + radius]; 658 } 659 660 //Now we need to normalize the weights because all our coefficients need to add up to one 661 normalizeFactor = 1.0f / normalizeFactor; 662 for (int32_t r = -radius; r <= radius; r ++) { 663 weights[r + radius] *= normalizeFactor; 664 } 665} 666 667void FontRenderer::horizontalBlur(float* weights, int32_t radius, 668 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 669 float blurredPixel = 0.0f; 670 float currentPixel = 0.0f; 671 672 for (int32_t y = 0; y < height; y ++) { 673 674 const uint8_t* input = source + y * width; 675 uint8_t* output = dest + y * width; 676 677 for (int32_t x = 0; x < width; x ++) { 678 blurredPixel = 0.0f; 679 const float* gPtr = weights; 680 // Optimization for non-border pixels 681 if (x > radius && x < (width - radius)) { 682 const uint8_t *i = input + (x - radius); 683 for (int r = -radius; r <= radius; r ++) { 684 currentPixel = (float) (*i); 685 blurredPixel += currentPixel * gPtr[0]; 686 gPtr++; 687 i++; 688 } 689 } else { 690 for (int32_t r = -radius; r <= radius; r ++) { 691 // Stepping left and right away from the pixel 692 int validW = x + r; 693 if (validW < 0) { 694 validW = 0; 695 } 696 if (validW > width - 1) { 697 validW = width - 1; 698 } 699 700 currentPixel = (float) input[validW]; 701 blurredPixel += currentPixel * gPtr[0]; 702 gPtr++; 703 } 704 } 705 *output = (uint8_t)blurredPixel; 706 output ++; 707 } 708 } 709} 710 711void FontRenderer::verticalBlur(float* weights, int32_t radius, 712 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 713 float blurredPixel = 0.0f; 714 float currentPixel = 0.0f; 715 716 for (int32_t y = 0; y < height; y ++) { 717 uint8_t* output = dest + y * width; 718 719 for (int32_t x = 0; x < width; x ++) { 720 blurredPixel = 0.0f; 721 const float* gPtr = weights; 722 const uint8_t* input = source + x; 723 // Optimization for non-border pixels 724 if (y > radius && y < (height - radius)) { 725 const uint8_t *i = input + ((y - radius) * width); 726 for (int32_t r = -radius; r <= radius; r ++) { 727 currentPixel = (float)(*i); 728 blurredPixel += currentPixel * gPtr[0]; 729 gPtr++; 730 i += width; 731 } 732 } else { 733 for (int32_t r = -radius; r <= radius; r ++) { 734 int validH = y + r; 735 // Clamp to zero and width 736 if (validH < 0) { 737 validH = 0; 738 } 739 if (validH > height - 1) { 740 validH = height - 1; 741 } 742 743 const uint8_t *i = input + validH * width; 744 currentPixel = (float) (*i); 745 blurredPixel += currentPixel * gPtr[0]; 746 gPtr++; 747 } 748 } 749 *output = (uint8_t) blurredPixel; 750 output++; 751 } 752 } 753} 754 755 756void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) { 757 float *gaussian = new float[2 * radius + 1]; 758 computeGaussianWeights(gaussian, radius); 759 760 uint8_t* scratch = new uint8_t[width * height]; 761 762 horizontalBlur(gaussian, radius, image, scratch, width, height); 763 verticalBlur(gaussian, radius, scratch, image, width, height); 764 765 delete[] gaussian; 766 delete[] scratch; 767} 768 769}; // namespace uirenderer 770}; // namespace android 771