FontRenderer.cpp revision 89a524ac2d4a36739e51b01b336c0bade77e2ee0
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#include <utils/Log.h> 23 24#include "FontRenderer.h" 25 26namespace android { 27namespace uirenderer { 28 29/////////////////////////////////////////////////////////////////////////////// 30// Defines 31/////////////////////////////////////////////////////////////////////////////// 32 33#define DEFAULT_TEXT_CACHE_WIDTH 1024 34#define DEFAULT_TEXT_CACHE_HEIGHT 256 35 36/////////////////////////////////////////////////////////////////////////////// 37// Font 38/////////////////////////////////////////////////////////////////////////////// 39 40Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) : 41 mState(state), mFontId(fontId), mFontSize(fontSize) { 42} 43 44 45Font::~Font() { 46 for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) { 47 if (mState->mActiveFonts[ct] == this) { 48 mState->mActiveFonts.removeAt(ct); 49 break; 50 } 51 } 52 53 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { 54 CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i); 55 delete glyph; 56 } 57} 58 59void Font::invalidateTextureCache() { 60 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { 61 mCachedGlyphs.valueAt(i)->mIsValid = false; 62 } 63} 64 65void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) { 66 int nPenX = x + glyph->mBitmapLeft; 67 int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight; 68 69 float u1 = glyph->mBitmapMinU; 70 float u2 = glyph->mBitmapMaxU; 71 float v1 = glyph->mBitmapMinV; 72 float v2 = glyph->mBitmapMaxV; 73 74 int width = (int) glyph->mBitmapWidth; 75 int height = (int) glyph->mBitmapHeight; 76 77 mState->appendMeshQuad(nPenX, nPenY, 0, u1, v2, 78 nPenX + width, nPenY, 0, u2, v2, 79 nPenX + width, nPenY - height, 0, u2, v1, 80 nPenX, nPenY - height, 0, u1, v1); 81} 82 83void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y, 84 uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { 85 int nPenX = x + glyph->mBitmapLeft; 86 int nPenY = y + glyph->mBitmapTop; 87 88 uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; 89 uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; 90 91 if(nPenX < 0 || nPenY < 0) { 92 LOGE("Cannot render into a bitmap, some of the glyph is below zero"); 93 return; 94 } 95 96 if(nPenX + glyph->mBitmapWidth >= bitmapW || nPenY + glyph->mBitmapHeight >= bitmapH) { 97 LOGE("Cannot render into a bitmap, dimentions too small"); 98 return; 99 } 100 101 uint32_t cacheWidth = mState->getCacheWidth(); 102 const uint8_t* cacheBuffer = mState->getTextTextureData(); 103 104 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 105 for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { 106 for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { 107 uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX]; 108 bitmap[bY * bitmapW + bX] = tempCol; 109 } 110 } 111 112} 113 114Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) { 115 CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(utfChar); 116 if (cachedGlyph == NULL) { 117 cachedGlyph = cacheGlyph(paint, utfChar); 118 } 119 120 // Is the glyph still in texture cache? 121 if (!cachedGlyph->mIsValid) { 122 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar); 123 updateGlyphCache(paint, skiaGlyph, cachedGlyph); 124 } 125 126 return cachedGlyph; 127} 128 129void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len, 130 int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { 131 if (numGlyphs == 0 || text == NULL || len == 0) { 132 return; 133 } 134 135 int penX = x, penY = y; 136 int glyphsLeft = 1; 137 if (numGlyphs > 0) { 138 glyphsLeft = numGlyphs; 139 } 140 141 text += start; 142 143 while (glyphsLeft > 0) { 144 int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text); 145 146 // Reached the end of the string or encountered 147 if (utfChar < 0) { 148 break; 149 } 150 151 CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar); 152 153 // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage 154 if (cachedGlyph->mIsValid) { 155 if(bitmap != NULL) { 156 drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH); 157 } 158 else { 159 drawCachedGlyph(cachedGlyph, penX, penY); 160 } 161 } 162 163 penX += SkFixedFloor(cachedGlyph->mAdvanceX); 164 165 // If we were given a specific number of glyphs, decrement 166 if (numGlyphs > 0) { 167 glyphsLeft--; 168 } 169 } 170} 171 172void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) { 173 glyph->mAdvanceX = skiaGlyph.fAdvanceX; 174 glyph->mAdvanceY = skiaGlyph.fAdvanceY; 175 glyph->mBitmapLeft = skiaGlyph.fLeft; 176 glyph->mBitmapTop = skiaGlyph.fTop; 177 178 uint32_t startX = 0; 179 uint32_t startY = 0; 180 181 // Get the bitmap for the glyph 182 paint->findImage(skiaGlyph); 183 glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY); 184 185 if (!glyph->mIsValid) { 186 return; 187 } 188 189 uint32_t endX = startX + skiaGlyph.fWidth; 190 uint32_t endY = startY + skiaGlyph.fHeight; 191 192 glyph->mStartX = startX; 193 glyph->mStartY = startY; 194 glyph->mBitmapWidth = skiaGlyph.fWidth; 195 glyph->mBitmapHeight = skiaGlyph.fHeight; 196 197 uint32_t cacheWidth = mState->getCacheWidth(); 198 uint32_t cacheHeight = mState->getCacheHeight(); 199 200 glyph->mBitmapMinU = (float) startX / (float) cacheWidth; 201 glyph->mBitmapMinV = (float) startY / (float) cacheHeight; 202 glyph->mBitmapMaxU = (float) endX / (float) cacheWidth; 203 glyph->mBitmapMaxV = (float) endY / (float) cacheHeight; 204 205 mState->mUploadTexture = true; 206} 207 208Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) { 209 CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); 210 mCachedGlyphs.add(glyph, newGlyph); 211 212 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph); 213 newGlyph->mGlyphIndex = skiaGlyph.fID; 214 newGlyph->mIsValid = false; 215 216 updateGlyphCache(paint, skiaGlyph, newGlyph); 217 218 return newGlyph; 219} 220 221Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) { 222 Vector<Font*> &activeFonts = state->mActiveFonts; 223 224 for (uint32_t i = 0; i < activeFonts.size(); i++) { 225 Font* font = activeFonts[i]; 226 if (font->mFontId == fontId && font->mFontSize == fontSize) { 227 return font; 228 } 229 } 230 231 Font* newFont = new Font(state, fontId, fontSize); 232 activeFonts.push(newFont); 233 return newFont; 234} 235 236/////////////////////////////////////////////////////////////////////////////// 237// FontRenderer 238/////////////////////////////////////////////////////////////////////////////// 239 240FontRenderer::FontRenderer() { 241 LOGD("Creating FontRenderer"); 242 243 mInitialized = false; 244 mMaxNumberOfQuads = 1024; 245 mCurrentQuadIndex = 0; 246 mTextureId = 0; 247 248 mIndexBufferID = 0; 249 250 mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH; 251 mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT; 252 253 char property[PROPERTY_VALUE_MAX]; 254 if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) { 255 LOGD(" Setting text cache width to %s pixels", property); 256 mCacheWidth = atoi(property); 257 } else { 258 LOGD(" Using default text cache width of %i pixels", mCacheWidth); 259 } 260 261 if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) { 262 LOGD(" Setting text cache width to %s pixels", property); 263 mCacheHeight = atoi(property); 264 } else { 265 LOGD(" Using default text cache height of %i pixels", mCacheHeight); 266 } 267} 268 269FontRenderer::~FontRenderer() { 270 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 271 delete mCacheLines[i]; 272 } 273 mCacheLines.clear(); 274 275 delete[] mTextMeshPtr; 276 delete[] mTextTexture; 277 278 if(mTextureId) { 279 glDeleteTextures(1, &mTextureId); 280 } 281 282 Vector<Font*> fontsToDereference = mActiveFonts; 283 for (uint32_t i = 0; i < fontsToDereference.size(); i++) { 284 delete fontsToDereference[i]; 285 } 286} 287 288void FontRenderer::flushAllAndInvalidate() { 289 if (mCurrentQuadIndex != 0) { 290 issueDrawCommand(); 291 mCurrentQuadIndex = 0; 292 } 293 for (uint32_t i = 0; i < mActiveFonts.size(); i++) { 294 mActiveFonts[i]->invalidateTextureCache(); 295 } 296 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 297 mCacheLines[i]->mCurrentCol = 0; 298 } 299} 300 301bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { 302 // If the glyph is too tall, don't cache it 303 if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { 304 LOGE("Font size to large to fit in cache. width, height = %i, %i", 305 (int) glyph.fWidth, (int) glyph.fHeight); 306 return false; 307 } 308 309 // Now copy the bitmap into the cache texture 310 uint32_t startX = 0; 311 uint32_t startY = 0; 312 313 bool bitmapFit = false; 314 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 315 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); 316 if (bitmapFit) { 317 break; 318 } 319 } 320 321 // If the new glyph didn't fit, flush the state so far and invalidate everything 322 if (!bitmapFit) { 323 flushAllAndInvalidate(); 324 325 // Try to fit it again 326 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 327 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); 328 if (bitmapFit) { 329 break; 330 } 331 } 332 333 // if we still don't fit, something is wrong and we shouldn't draw 334 if (!bitmapFit) { 335 LOGE("Bitmap doesn't fit in cache. width, height = %i, %i", 336 (int) glyph.fWidth, (int) glyph.fHeight); 337 return false; 338 } 339 } 340 341 *retOriginX = startX; 342 *retOriginY = startY; 343 344 uint32_t endX = startX + glyph.fWidth; 345 uint32_t endY = startY + glyph.fHeight; 346 347 uint32_t cacheWidth = mCacheWidth; 348 349 uint8_t* cacheBuffer = mTextTexture; 350 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 351 unsigned int stride = glyph.rowBytes(); 352 353 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 354 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 355 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) { 356 uint8_t tempCol = bitmapBuffer[bY * stride + bX]; 357 cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol; 358 } 359 } 360 361 return true; 362} 363 364void FontRenderer::initTextTexture() { 365 mTextTexture = new uint8_t[mCacheWidth * mCacheHeight]; 366 mUploadTexture = false; 367 368 glGenTextures(1, &mTextureId); 369 glBindTexture(GL_TEXTURE_2D, mTextureId); 370 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 371 // Initialize texture dimentions 372 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0, 373 GL_ALPHA, GL_UNSIGNED_BYTE, 0); 374 375 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 376 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 377 378 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 379 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 380 381 // Split up our cache texture into lines of certain widths 382 int nextLine = 0; 383 mCacheLines.push(new CacheTextureLine(mCacheWidth, 16, nextLine, 0)); 384 nextLine += mCacheLines.top()->mMaxHeight; 385 mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0)); 386 nextLine += mCacheLines.top()->mMaxHeight; 387 mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0)); 388 nextLine += mCacheLines.top()->mMaxHeight; 389 mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0)); 390 nextLine += mCacheLines.top()->mMaxHeight; 391 mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0)); 392 nextLine += mCacheLines.top()->mMaxHeight; 393 mCacheLines.push(new CacheTextureLine(mCacheWidth, 40, nextLine, 0)); 394 nextLine += mCacheLines.top()->mMaxHeight; 395 mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0)); 396} 397 398// Avoid having to reallocate memory and render quad by quad 399void FontRenderer::initVertexArrayBuffers() { 400 uint32_t numIndicies = mMaxNumberOfQuads * 6; 401 uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t); 402 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); 403 404 // Four verts, two triangles , six indices per quad 405 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) { 406 int i6 = i * 6; 407 int i4 = i * 4; 408 409 indexBufferData[i6 + 0] = i4 + 0; 410 indexBufferData[i6 + 1] = i4 + 1; 411 indexBufferData[i6 + 2] = i4 + 2; 412 413 indexBufferData[i6 + 3] = i4 + 0; 414 indexBufferData[i6 + 4] = i4 + 2; 415 indexBufferData[i6 + 5] = i4 + 3; 416 } 417 418 glGenBuffers(1, &mIndexBufferID); 419 glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID); 420 glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW); 421 glBindBuffer(GL_ARRAY_BUFFER, 0); 422 423 free(indexBufferData); 424 425 uint32_t coordSize = 3; 426 uint32_t uvSize = 2; 427 uint32_t vertsPerQuad = 4; 428 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize; 429 mTextMeshPtr = new float[vertexBufferSize]; 430} 431 432// We don't want to allocate anything unless we actually draw text 433void FontRenderer::checkInit() { 434 if (mInitialized) { 435 return; 436 } 437 438 initTextTexture(); 439 initVertexArrayBuffers(); 440 441 // We store a string with letters in a rough frequency of occurrence 442 mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq "); 443 mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ"); 444 mLatinPrecache += String16(",.?!()-+@;:`'"); 445 mLatinPrecache += String16("0123456789"); 446 447 mInitialized = true; 448} 449 450void FontRenderer::checkTextureUpdate() { 451 if (!mUploadTexture) { 452 return; 453 } 454 455 glBindTexture(GL_TEXTURE_2D, mTextureId); 456 457 // Iterate over all the cache lines and see which ones need to be updated 458 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 459 CacheTextureLine* cl = mCacheLines[i]; 460 if(cl->mDirty) { 461 uint32_t xOffset = 0; 462 uint32_t yOffset = cl->mCurrentRow; 463 uint32_t width = mCacheWidth; 464 uint32_t height = cl->mMaxHeight; 465 void* textureData = mTextTexture + yOffset*width; 466 467 glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, 468 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 469 470 cl->mDirty = false; 471 } 472 } 473 474 mUploadTexture = false; 475} 476 477void FontRenderer::issueDrawCommand() { 478 479 checkTextureUpdate(); 480 481 float* vtx = mTextMeshPtr; 482 float* tex = vtx + 3; 483 484 // position is slot 0 485 uint32_t slot = 0; 486 glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx); 487 488 // texture0 is slot 1 489 slot = 1; 490 glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex); 491 492 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID); 493 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL); 494} 495 496void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2, 497 float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3, 498 float x4, float y4, float z4, float u4, float v4) { 499 if (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom) { 500 return; 501 } 502 503 const uint32_t vertsPerQuad = 4; 504 const uint32_t floatsPerVert = 5; 505 float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert; 506 507 (*currentPos++) = x1; 508 (*currentPos++) = y1; 509 (*currentPos++) = z1; 510 (*currentPos++) = u1; 511 (*currentPos++) = v1; 512 513 (*currentPos++) = x2; 514 (*currentPos++) = y2; 515 (*currentPos++) = z2; 516 (*currentPos++) = u2; 517 (*currentPos++) = v2; 518 519 (*currentPos++) = x3; 520 (*currentPos++) = y3; 521 (*currentPos++) = z3; 522 (*currentPos++) = u3; 523 (*currentPos++) = v3; 524 525 (*currentPos++) = x4; 526 (*currentPos++) = y4; 527 (*currentPos++) = z4; 528 (*currentPos++) = u4; 529 (*currentPos++) = v4; 530 531 mCurrentQuadIndex++; 532 533 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 534 issueDrawCommand(); 535 mCurrentQuadIndex = 0; 536 } 537} 538 539uint32_t FontRenderer::getRemainingCacheCapacity() { 540 uint32_t remainingCapacity = 0; 541 float totalPixels = 0; 542 for(uint32_t i = 0; i < mCacheLines.size(); i ++) { 543 remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol); 544 totalPixels += mCacheLines[i]->mMaxWidth; 545 } 546 remainingCapacity = (remainingCapacity * 100) / totalPixels; 547 return remainingCapacity; 548} 549 550void FontRenderer::precacheLatin(SkPaint* paint) { 551 // Remaining capacity is measured in % 552 uint32_t remainingCapacity = getRemainingCacheCapacity(); 553 uint32_t precacheIdx = 0; 554 while(remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) { 555 mCurrentFont->getCachedUTFChar(paint, (int32_t)mLatinPrecache[precacheIdx]); 556 remainingCapacity = getRemainingCacheCapacity(); 557 precacheIdx ++; 558 } 559} 560 561void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { 562 uint32_t currentNumFonts = mActiveFonts.size(); 563 mCurrentFont = Font::create(this, fontId, fontSize); 564 565 const float maxPrecacheFontSize = 40.0f; 566 bool isNewFont = currentNumFonts != mActiveFonts.size(); 567 568 if(isNewFont && fontSize <= maxPrecacheFontSize ){ 569 precacheLatin(paint); 570 } 571} 572 573void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text, 574 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) { 575 checkInit(); 576 577 if (!mCurrentFont) { 578 LOGE("No font set"); 579 return; 580 } 581 582 mClip = clip; 583 mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y); 584 585 if (mCurrentQuadIndex != 0) { 586 issueDrawCommand(); 587 mCurrentQuadIndex = 0; 588 } 589} 590 591void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) { 592 // Compute gaussian weights for the blur 593 // e is the euler's number 594 float e = 2.718281828459045f; 595 float pi = 3.1415926535897932f; 596 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 ) 597 // x is of the form [-radius .. 0 .. radius] 598 // and sigma varies with radius. 599 // Based on some experimental radius values and sigma's 600 // we approximately fit sigma = f(radius) as 601 // sigma = radius * 0.4 + 0.6 602 // The larger the radius gets, the more our gaussian blur 603 // will resemble a box blur since with large sigma 604 // the gaussian curve begins to lose its shape 605 float sigma = 0.4f * (float)radius + 0.6f; 606 607 // Now compute the coefficints 608 // We will store some redundant values to save some math during 609 // the blur calculations 610 // precompute some values 611 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma); 612 float coeff2 = - 1.0f / (2.0f * sigma * sigma); 613 614 float normalizeFactor = 0.0f; 615 for(int32_t r = -radius; r <= radius; r ++) { 616 float floatR = (float)r; 617 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2); 618 normalizeFactor += weights[r + radius]; 619 } 620 621 //Now we need to normalize the weights because all our coefficients need to add up to one 622 normalizeFactor = 1.0f / normalizeFactor; 623 for(int32_t r = -radius; r <= radius; r ++) { 624 weights[r + radius] *= normalizeFactor; 625 } 626} 627 628void FontRenderer::horizontalBlur(float* weights, int32_t radius, 629 const uint8_t* source, uint8_t* dest, 630 int32_t width, int32_t height) { 631 float blurredPixel = 0.0f; 632 float currentPixel = 0.0f; 633 634 for(int32_t y = 0; y < height; y ++) { 635 636 const uint8_t* input = source + y * width; 637 uint8_t* output = dest + y * width; 638 639 for(int32_t x = 0; x < width; x ++) { 640 blurredPixel = 0.0f; 641 const float* gPtr = weights; 642 // Optimization for non-border pixels 643 if ((x > radius) && (x < (width - radius))) { 644 const uint8_t *i = input + (x - radius); 645 for(int r = -radius; r <= radius; r ++) { 646 currentPixel = (float)(*i); 647 blurredPixel += currentPixel * gPtr[0]; 648 gPtr++; 649 i++; 650 } 651 } else { 652 for(int32_t r = -radius; r <= radius; r ++) { 653 // Stepping left and right away from the pixel 654 int validW = x + r; 655 if(validW < 0) { 656 validW = 0; 657 } 658 if(validW > width - 1) { 659 validW = width - 1; 660 } 661 662 currentPixel = (float)(input[validW]); 663 blurredPixel += currentPixel * gPtr[0]; 664 gPtr++; 665 } 666 } 667 *output = (uint8_t)blurredPixel; 668 output ++; 669 } 670 } 671} 672 673void FontRenderer::verticalBlur(float* weights, int32_t radius, 674 const uint8_t* source, uint8_t* dest, 675 int32_t width, int32_t height) { 676 float blurredPixel = 0.0f; 677 float currentPixel = 0.0f; 678 679 for(int32_t y = 0; y < height; y ++) { 680 681 uint8_t* output = dest + y * width; 682 683 for(int32_t x = 0; x < width; x ++) { 684 blurredPixel = 0.0f; 685 const float* gPtr = weights; 686 const uint8_t* input = source + x; 687 // Optimization for non-border pixels 688 if ((y > radius) && (y < (height - radius))) { 689 const uint8_t *i = input + ((y - radius) * width); 690 for(int32_t r = -radius; r <= radius; r ++) { 691 currentPixel = (float)(*i); 692 blurredPixel += currentPixel * gPtr[0]; 693 gPtr++; 694 i += width; 695 } 696 } else { 697 for(int32_t r = -radius; r <= radius; r ++) { 698 int validH = y + r; 699 // Clamp to zero and width 700 if(validH < 0) { 701 validH = 0; 702 } 703 if(validH > height - 1) { 704 validH = height - 1; 705 } 706 707 const uint8_t *i = input + validH * width; 708 currentPixel = (float)(*i); 709 blurredPixel += currentPixel * gPtr[0]; 710 gPtr++; 711 } 712 } 713 *output = (uint8_t)blurredPixel; 714 output ++; 715 } 716 } 717} 718 719 720void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) { 721 float *gaussian = new float[2 * radius + 1]; 722 computeGaussianWeights(gaussian, radius); 723 uint8_t* scratch = new uint8_t[width * height]; 724 horizontalBlur(gaussian, radius, image, scratch, width, height); 725 verticalBlur(gaussian, radius, scratch, image, width, height); 726 delete[] gaussian; 727 delete[] scratch; 728} 729 730}; // namespace uirenderer 731}; // namespace android 732