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