FontRenderer.cpp revision 19d4f1806a953b87435a2bb6fd8e738424e94003
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 uint8_t* cacheBuffer = cacheTexture->getTexture(); 223 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 224 unsigned int stride = glyph.rowBytes(); 225 226 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 227 228 for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) { 229 cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0; 230 cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0; 231 } 232 233 for (cacheY = startY - TEXTURE_BORDER_SIZE + 1; 234 cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) { 235 cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0; 236 cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0; 237 } 238 239 if (mGammaTable) { 240 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 241 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) { 242 uint8_t tempCol = bitmapBuffer[bY * stride + bX]; 243 cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol]; 244 } 245 } 246 } else { 247 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 248 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) { 249 uint8_t tempCol = bitmapBuffer[bY * stride + bX]; 250 cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol; 251 } 252 } 253 } 254 255 cachedGlyph->mIsValid = true; 256} 257 258CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { 259 CacheTexture* cacheTexture = new CacheTexture(width, height); 260 261 if (allocate) { 262 Caches::getInstance().activeTexture(0); 263 cacheTexture->allocateTexture(); 264 } 265 266 return cacheTexture; 267} 268 269void FontRenderer::initTextTexture() { 270 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 271 delete mCacheTextures[i]; 272 } 273 mCacheTextures.clear(); 274 275 mUploadTexture = false; 276 mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); 277 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); 278 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); 279 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false)); 280 mCurrentCacheTexture = mCacheTextures[0]; 281} 282 283// Avoid having to reallocate memory and render quad by quad 284void FontRenderer::initVertexArrayBuffers() { 285 uint32_t numIndices = mMaxNumberOfQuads * 6; 286 uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); 287 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); 288 289 // Four verts, two triangles , six indices per quad 290 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) { 291 int i6 = i * 6; 292 int i4 = i * 4; 293 294 indexBufferData[i6 + 0] = i4 + 0; 295 indexBufferData[i6 + 1] = i4 + 1; 296 indexBufferData[i6 + 2] = i4 + 2; 297 298 indexBufferData[i6 + 3] = i4 + 0; 299 indexBufferData[i6 + 4] = i4 + 2; 300 indexBufferData[i6 + 5] = i4 + 3; 301 } 302 303 glGenBuffers(1, &mIndexBufferID); 304 Caches::getInstance().bindIndicesBuffer(mIndexBufferID); 305 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); 306 307 free(indexBufferData); 308 309 uint32_t coordSize = 2; 310 uint32_t uvSize = 2; 311 uint32_t vertsPerQuad = 4; 312 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize; 313 mTextMesh = new float[vertexBufferSize]; 314} 315 316// We don't want to allocate anything unless we actually draw text 317void FontRenderer::checkInit() { 318 if (mInitialized) { 319 return; 320 } 321 322 initTextTexture(); 323 initVertexArrayBuffers(); 324 325 mInitialized = true; 326} 327 328void FontRenderer::updateDrawParams() { 329 if (mCurrentQuadIndex != mLastQuadIndex) { 330 mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6)); 331 mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex); 332 mDrawCacheTextures.add(mCurrentCacheTexture); 333 mLastQuadIndex = mCurrentQuadIndex; 334 } 335} 336 337void FontRenderer::checkTextureUpdate() { 338 if (!mUploadTexture) { 339 return; 340 } 341 342 Caches& caches = Caches::getInstance(); 343 GLuint lastTextureId = 0; 344 // Iterate over all the cache textures and see which ones need to be updated 345 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 346 CacheTexture* cacheTexture = mCacheTextures[i]; 347 if (cacheTexture->isDirty() && cacheTexture->getTexture()) { 348 // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer 349 // of data. So expand the dirty rect to the encompassing horizontal stripe. 350 const Rect* dirtyRect = cacheTexture->getDirtyRect(); 351 uint32_t x = 0; 352 uint32_t y = dirtyRect->top; 353 uint32_t width = cacheTexture->getWidth(); 354 uint32_t height = dirtyRect->getHeight(); 355 void* textureData = cacheTexture->getTexture() + y * width; 356 357 if (cacheTexture->getTextureId() != lastTextureId) { 358 lastTextureId = cacheTexture->getTextureId(); 359 caches.activeTexture(0); 360 glBindTexture(GL_TEXTURE_2D, lastTextureId); 361 } 362#if DEBUG_FONT_RENDERER 363 ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", 364 i, x, y, width, height); 365#endif 366 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, 367 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 368 cacheTexture->setDirty(false); 369 } 370 } 371 372 mUploadTexture = false; 373} 374 375void FontRenderer::issueDrawCommand() { 376 updateDrawParams(); 377 checkTextureUpdate(); 378 379 Caches& caches = Caches::getInstance(); 380 caches.bindIndicesBuffer(mIndexBufferID); 381 if (!mDrawn) { 382 float* buffer = mTextMesh; 383 int offset = 2; 384 385 bool force = caches.unbindMeshBuffer(); 386 caches.bindPositionVertexPointer(force, buffer); 387 caches.bindTexCoordsVertexPointer(force, buffer + offset); 388 } 389 390 for (uint32_t i = 0; i < mDrawOffsets.size(); i++) { 391 uint16_t* offset = mDrawOffsets[i]; 392 uint32_t count = mDrawCounts[i]; 393 CacheTexture* texture = mDrawCacheTextures[i]; 394 395 caches.activeTexture(0); 396 glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); 397 398 texture->setLinearFiltering(mLinearFiltering, false); 399 400 glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset); 401 } 402 403 mDrawn = true; 404 405 mCurrentQuadIndex = 0; 406 mLastQuadIndex = 0; 407 mDrawOffsets.clear(); 408 mDrawCounts.clear(); 409 mDrawCacheTextures.clear(); 410} 411 412void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, 413 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 414 float x4, float y4, float u4, float v4, CacheTexture* texture) { 415 if (texture != mCurrentCacheTexture) { 416 updateDrawParams(); 417 // Now use the new texture id 418 mCurrentCacheTexture = texture; 419 } 420 421 const uint32_t vertsPerQuad = 4; 422 const uint32_t floatsPerVert = 4; 423 float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert; 424 425 (*currentPos++) = x1; 426 (*currentPos++) = y1; 427 (*currentPos++) = u1; 428 (*currentPos++) = v1; 429 430 (*currentPos++) = x2; 431 (*currentPos++) = y2; 432 (*currentPos++) = u2; 433 (*currentPos++) = v2; 434 435 (*currentPos++) = x3; 436 (*currentPos++) = y3; 437 (*currentPos++) = u3; 438 (*currentPos++) = v3; 439 440 (*currentPos++) = x4; 441 (*currentPos++) = y4; 442 (*currentPos++) = u4; 443 (*currentPos++) = v4; 444 445 mCurrentQuadIndex++; 446} 447 448void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, 449 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 450 float x4, float y4, float u4, float v4, CacheTexture* texture) { 451 452 if (mClip && 453 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { 454 return; 455 } 456 457 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 458 459 if (mBounds) { 460 mBounds->left = fmin(mBounds->left, x1); 461 mBounds->top = fmin(mBounds->top, y3); 462 mBounds->right = fmax(mBounds->right, x3); 463 mBounds->bottom = fmax(mBounds->bottom, y1); 464 } 465 466 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 467 issueDrawCommand(); 468 } 469} 470 471void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, 472 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 473 float x4, float y4, float u4, float v4, CacheTexture* texture) { 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, fmin(x1, fmin(x2, fmin(x3, x4)))); 479 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); 480 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); 481 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); 482 } 483 484 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 485 issueDrawCommand(); 486 } 487} 488 489void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) { 490 mCurrentFont = Font::create(this, paint, matrix); 491} 492 493FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, 494 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) { 495 checkInit(); 496 497 if (!mCurrentFont) { 498 DropShadow image; 499 image.width = 0; 500 image.height = 0; 501 image.image = NULL; 502 image.penX = 0; 503 image.penY = 0; 504 return image; 505 } 506 507 mDrawn = false; 508 mClip = NULL; 509 mBounds = NULL; 510 511 Rect bounds; 512 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); 513 514 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; 515 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; 516 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight]; 517 518 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) { 519 dataBuffer[i] = 0; 520 } 521 522 int penX = radius - bounds.left; 523 int penY = radius - bounds.bottom; 524 525 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, 526 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions); 527 blurImage(dataBuffer, paddedWidth, paddedHeight, radius); 528 529 DropShadow image; 530 image.width = paddedWidth; 531 image.height = paddedHeight; 532 image.image = dataBuffer; 533 image.penX = penX; 534 image.penY = penY; 535 536 return image; 537} 538 539void FontRenderer::initRender(const Rect* clip, Rect* bounds) { 540 checkInit(); 541 542 mDrawn = false; 543 mBounds = bounds; 544 mClip = clip; 545} 546 547void FontRenderer::finishRender() { 548 mBounds = NULL; 549 mClip = NULL; 550 551 if (mCurrentQuadIndex != 0) { 552 issueDrawCommand(); 553 } 554} 555 556void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) { 557 Font* font = Font::create(this, paint, matrix); 558 font->precache(paint, text, numGlyphs); 559} 560 561bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, 562 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, 563 const float* positions, Rect* bounds) { 564 if (!mCurrentFont) { 565 ALOGE("No font set"); 566 return false; 567 } 568 569 initRender(clip, bounds); 570 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); 571 finishRender(); 572 573 return mDrawn; 574} 575 576bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, 577 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, 578 float hOffset, float vOffset, Rect* bounds) { 579 if (!mCurrentFont) { 580 ALOGE("No font set"); 581 return false; 582 } 583 584 initRender(clip, bounds); 585 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); 586 finishRender(); 587 588 return mDrawn; 589} 590 591void FontRenderer::removeFont(const Font* font) { 592 mActiveFonts.remove(font->getDescription()); 593 594 if (mCurrentFont == font) { 595 mCurrentFont = NULL; 596 } 597} 598 599void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) { 600 // Compute gaussian weights for the blur 601 // e is the euler's number 602 float e = 2.718281828459045f; 603 float pi = 3.1415926535897932f; 604 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 ) 605 // x is of the form [-radius .. 0 .. radius] 606 // and sigma varies with radius. 607 // Based on some experimental radius values and sigma's 608 // we approximately fit sigma = f(radius) as 609 // sigma = radius * 0.3 + 0.6 610 // The larger the radius gets, the more our gaussian blur 611 // will resemble a box blur since with large sigma 612 // the gaussian curve begins to lose its shape 613 float sigma = 0.3f * (float) radius + 0.6f; 614 615 // Now compute the coefficints 616 // We will store some redundant values to save some math during 617 // the blur calculations 618 // precompute some values 619 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma); 620 float coeff2 = - 1.0f / (2.0f * sigma * sigma); 621 622 float normalizeFactor = 0.0f; 623 for (int32_t r = -radius; r <= radius; r ++) { 624 float floatR = (float) r; 625 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2); 626 normalizeFactor += weights[r + radius]; 627 } 628 629 //Now we need to normalize the weights because all our coefficients need to add up to one 630 normalizeFactor = 1.0f / normalizeFactor; 631 for (int32_t r = -radius; r <= radius; r ++) { 632 weights[r + radius] *= normalizeFactor; 633 } 634} 635 636void FontRenderer::horizontalBlur(float* weights, int32_t radius, 637 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 638 float blurredPixel = 0.0f; 639 float currentPixel = 0.0f; 640 641 for (int32_t y = 0; y < height; y ++) { 642 643 const uint8_t* input = source + y * width; 644 uint8_t* output = dest + y * width; 645 646 for (int32_t x = 0; x < width; x ++) { 647 blurredPixel = 0.0f; 648 const float* gPtr = weights; 649 // Optimization for non-border pixels 650 if (x > radius && x < (width - radius)) { 651 const uint8_t *i = input + (x - radius); 652 for (int r = -radius; r <= radius; r ++) { 653 currentPixel = (float) (*i); 654 blurredPixel += currentPixel * gPtr[0]; 655 gPtr++; 656 i++; 657 } 658 } else { 659 for (int32_t r = -radius; r <= radius; r ++) { 660 // Stepping left and right away from the pixel 661 int validW = x + r; 662 if (validW < 0) { 663 validW = 0; 664 } 665 if (validW > width - 1) { 666 validW = width - 1; 667 } 668 669 currentPixel = (float) input[validW]; 670 blurredPixel += currentPixel * gPtr[0]; 671 gPtr++; 672 } 673 } 674 *output = (uint8_t)blurredPixel; 675 output ++; 676 } 677 } 678} 679 680void FontRenderer::verticalBlur(float* weights, int32_t radius, 681 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 682 float blurredPixel = 0.0f; 683 float currentPixel = 0.0f; 684 685 for (int32_t y = 0; y < height; y ++) { 686 uint8_t* output = dest + y * width; 687 688 for (int32_t x = 0; x < width; x ++) { 689 blurredPixel = 0.0f; 690 const float* gPtr = weights; 691 const uint8_t* input = source + x; 692 // Optimization for non-border pixels 693 if (y > radius && y < (height - radius)) { 694 const uint8_t *i = input + ((y - radius) * width); 695 for (int32_t r = -radius; r <= radius; r ++) { 696 currentPixel = (float)(*i); 697 blurredPixel += currentPixel * gPtr[0]; 698 gPtr++; 699 i += width; 700 } 701 } else { 702 for (int32_t r = -radius; r <= radius; r ++) { 703 int validH = y + r; 704 // Clamp to zero and width 705 if (validH < 0) { 706 validH = 0; 707 } 708 if (validH > height - 1) { 709 validH = height - 1; 710 } 711 712 const uint8_t *i = input + validH * width; 713 currentPixel = (float) (*i); 714 blurredPixel += currentPixel * gPtr[0]; 715 gPtr++; 716 } 717 } 718 *output = (uint8_t) blurredPixel; 719 output++; 720 } 721 } 722} 723 724 725void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) { 726 float *gaussian = new float[2 * radius + 1]; 727 computeGaussianWeights(gaussian, radius); 728 729 uint8_t* scratch = new uint8_t[width * height]; 730 731 horizontalBlur(gaussian, radius, image, scratch, width, height); 732 verticalBlur(gaussian, radius, scratch, image, width, height); 733 734 delete[] gaussian; 735 delete[] scratch; 736} 737 738}; // namespace uirenderer 739}; // namespace android 740