FontRenderer.cpp revision ae91c4cbc79ea910753be65e2f1d7899abcb4da2
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 "Caches.h" 29 30namespace android { 31namespace uirenderer { 32 33/////////////////////////////////////////////////////////////////////////////// 34// Defines 35/////////////////////////////////////////////////////////////////////////////// 36 37#define DEFAULT_TEXT_CACHE_WIDTH 1024 38#define DEFAULT_TEXT_CACHE_HEIGHT 256 39#define MAX_TEXT_CACHE_WIDTH 2048 40#define TEXTURE_BORDER_SIZE 2 41 42#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16) 43 44/////////////////////////////////////////////////////////////////////////////// 45// CacheTextureLine 46/////////////////////////////////////////////////////////////////////////////// 47 48bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { 49 if (glyph.fHeight + TEXTURE_BORDER_SIZE > mMaxHeight) { 50 return false; 51 } 52 53 if (mCurrentCol + glyph.fWidth + TEXTURE_BORDER_SIZE < mMaxWidth) { 54 *retOriginX = mCurrentCol + 1; 55 *retOriginY = mCurrentRow + 1; 56 mCurrentCol += glyph.fWidth + TEXTURE_BORDER_SIZE; 57 mDirty = true; 58 return true; 59 } 60 61 return false; 62} 63 64/////////////////////////////////////////////////////////////////////////////// 65// Font 66/////////////////////////////////////////////////////////////////////////////// 67 68Font::Font(FontRenderer* state, uint32_t fontId, float fontSize, 69 int flags, uint32_t italicStyle, uint32_t scaleX, 70 SkPaint::Style style, uint32_t strokeWidth) : 71 mState(state), mFontId(fontId), mFontSize(fontSize), 72 mFlags(flags), mItalicStyle(italicStyle), mScaleX(scaleX), 73 mStyle(style), mStrokeWidth(mStrokeWidth) { 74} 75 76 77Font::~Font() { 78 for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) { 79 if (mState->mActiveFonts[ct] == this) { 80 mState->mActiveFonts.removeAt(ct); 81 break; 82 } 83 } 84 85 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { 86 delete mCachedGlyphs.valueAt(i); 87 } 88} 89 90void Font::invalidateTextureCache(CacheTextureLine *cacheLine) { 91 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { 92 CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i); 93 if (cacheLine == NULL || cachedGlyph->mCachedTextureLine == cacheLine) { 94 cachedGlyph->mIsValid = false; 95 } 96 } 97} 98 99void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, 100 uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { 101 int nPenX = x + glyph->mBitmapLeft; 102 int nPenY = y + glyph->mBitmapTop; 103 104 int width = (int) glyph->mBitmapWidth; 105 int height = (int) glyph->mBitmapHeight; 106 107 if (bounds->bottom > nPenY) { 108 bounds->bottom = nPenY; 109 } 110 if (bounds->left > nPenX) { 111 bounds->left = nPenX; 112 } 113 if (bounds->right < nPenX + width) { 114 bounds->right = nPenX + width; 115 } 116 if (bounds->top < nPenY + height) { 117 bounds->top = nPenY + height; 118 } 119} 120 121void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, 122 uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { 123 int nPenX = x + glyph->mBitmapLeft; 124 int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight; 125 126 float u1 = glyph->mBitmapMinU; 127 float u2 = glyph->mBitmapMaxU; 128 float v1 = glyph->mBitmapMinV; 129 float v2 = glyph->mBitmapMaxV; 130 131 int width = (int) glyph->mBitmapWidth; 132 int height = (int) glyph->mBitmapHeight; 133 134 mState->appendMeshQuad(nPenX, nPenY, u1, v2, 135 nPenX + width, nPenY, u2, v2, 136 nPenX + width, nPenY - height, u2, v1, 137 nPenX, nPenY - height, u1, v1, glyph->mCachedTextureLine->mCacheTexture); 138} 139 140void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, 141 uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { 142 int nPenX = x + glyph->mBitmapLeft; 143 int nPenY = y + glyph->mBitmapTop; 144 145 uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; 146 uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; 147 148 CacheTexture *cacheTexture = glyph->mCachedTextureLine->mCacheTexture; 149 uint32_t cacheWidth = cacheTexture->mWidth; 150 const uint8_t* cacheBuffer = cacheTexture->mTexture; 151 152 uint32_t cacheX = 0, cacheY = 0; 153 int32_t bX = 0, bY = 0; 154 for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) { 155 for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) { 156#if DEBUG_FONT_RENDERER 157 if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) { 158 ALOGE("Skipping invalid index"); 159 continue; 160 } 161#endif 162 uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX]; 163 bitmap[bY * bitmapW + bX] = tempCol; 164 } 165 } 166} 167 168void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, 169 SkPathMeasure& measure, SkPoint* position, SkVector* tangent) { 170 const float halfWidth = glyph->mBitmapWidth * 0.5f; 171 const float height = glyph->mBitmapHeight; 172 173 vOffset += glyph->mBitmapTop + height; 174 175 SkPoint destination[4]; 176 measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent); 177 178 // Move along the tangent and offset by the normal 179 destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset, 180 -tangent->fY * halfWidth + tangent->fX * vOffset); 181 destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset, 182 tangent->fY * halfWidth + tangent->fX * vOffset); 183 destination[2].set(destination[1].fX + tangent->fY * height, 184 destination[1].fY - tangent->fX * height); 185 destination[3].set(destination[0].fX + tangent->fY * height, 186 destination[0].fY - tangent->fX * height); 187 188 const float u1 = glyph->mBitmapMinU; 189 const float u2 = glyph->mBitmapMaxU; 190 const float v1 = glyph->mBitmapMinV; 191 const float v2 = glyph->mBitmapMaxV; 192 193 mState->appendRotatedMeshQuad( 194 position->fX + destination[0].fX, 195 position->fY + destination[0].fY, u1, v2, 196 position->fX + destination[1].fX, 197 position->fY + destination[1].fY, u2, v2, 198 position->fX + destination[2].fX, 199 position->fY + destination[2].fY, u2, v1, 200 position->fX + destination[3].fX, 201 position->fY + destination[3].fY, u1, v1, 202 glyph->mCachedTextureLine->mCacheTexture); 203} 204 205CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) { 206 CachedGlyphInfo* cachedGlyph = NULL; 207 ssize_t index = mCachedGlyphs.indexOfKey(textUnit); 208 if (index >= 0) { 209 cachedGlyph = mCachedGlyphs.valueAt(index); 210 } else { 211 cachedGlyph = cacheGlyph(paint, textUnit); 212 } 213 214 // Is the glyph still in texture cache? 215 if (!cachedGlyph->mIsValid) { 216 const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit); 217 updateGlyphCache(paint, skiaGlyph, cachedGlyph); 218 } 219 220 return cachedGlyph; 221} 222 223void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, 224 int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) { 225 if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) { 226 render(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap, 227 bitmapW, bitmapH, NULL, NULL); 228 } else { 229 render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 230 0, 0, NULL, NULL); 231 } 232} 233 234void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, 235 int numGlyphs, int x, int y, const float* positions) { 236 render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 237 0, 0, NULL, positions); 238} 239 240void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, 241 int numGlyphs, SkPath* path, float hOffset, float vOffset) { 242 if (numGlyphs == 0 || text == NULL || len == 0) { 243 return; 244 } 245 246 text += start; 247 248 int glyphsCount = 0; 249 SkFixed prevRsbDelta = 0; 250 251 float penX = 0.0f; 252 253 SkPoint position; 254 SkVector tangent; 255 256 SkPathMeasure measure(*path, false); 257 float pathLength = SkScalarToFloat(measure.getLength()); 258 259 if (paint->getTextAlign() != SkPaint::kLeft_Align) { 260 float textWidth = SkScalarToFloat(paint->measureText(text, len)); 261 float pathOffset = pathLength; 262 if (paint->getTextAlign() == SkPaint::kCenter_Align) { 263 textWidth *= 0.5f; 264 pathOffset *= 0.5f; 265 } 266 penX += pathOffset - textWidth; 267 } 268 269 while (glyphsCount < numGlyphs && penX < pathLength) { 270 glyph_t glyph = GET_GLYPH(text); 271 272 if (IS_END_OF_STRING(glyph)) { 273 break; 274 } 275 276 CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); 277 penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); 278 prevRsbDelta = cachedGlyph->mRsbDelta; 279 280 if (cachedGlyph->mIsValid) { 281 drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent); 282 } 283 284 penX += SkFixedToFloat(cachedGlyph->mAdvanceX); 285 286 glyphsCount++; 287 } 288} 289 290void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len, 291 int numGlyphs, Rect *bounds) { 292 if (bounds == NULL) { 293 ALOGE("No return rectangle provided to measure text"); 294 return; 295 } 296 bounds->set(1e6, -1e6, -1e6, 1e6); 297 render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, NULL); 298} 299 300void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, 301 int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, 302 uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { 303 if (numGlyphs == 0 || text == NULL || len == 0) { 304 return; 305 } 306 307 static RenderGlyph gRenderGlyph[] = { 308 &android::uirenderer::Font::drawCachedGlyph, 309 &android::uirenderer::Font::drawCachedGlyphBitmap, 310 &android::uirenderer::Font::measureCachedGlyph 311 }; 312 RenderGlyph render = gRenderGlyph[mode]; 313 314 text += start; 315 int glyphsCount = 0; 316 317 if (CC_LIKELY(positions == NULL)) { 318 SkFixed prevRsbDelta = 0; 319 320 float penX = x + 0.5f; 321 int penY = y; 322 323 while (glyphsCount < numGlyphs) { 324 glyph_t glyph = GET_GLYPH(text); 325 326 // Reached the end of the string 327 if (IS_END_OF_STRING(glyph)) { 328 break; 329 } 330 331 CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); 332 penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta)); 333 prevRsbDelta = cachedGlyph->mRsbDelta; 334 335 // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage 336 if (cachedGlyph->mIsValid) { 337 (*this.*render)(cachedGlyph, (int) floorf(penX), penY, 338 bitmap, bitmapW, bitmapH, bounds, positions); 339 } 340 341 penX += SkFixedToFloat(cachedGlyph->mAdvanceX); 342 343 glyphsCount++; 344 } 345 } else { 346 const SkPaint::Align align = paint->getTextAlign(); 347 348 // This is for renderPosText() 349 while (glyphsCount < numGlyphs) { 350 glyph_t glyph = GET_GLYPH(text); 351 352 // Reached the end of the string 353 if (IS_END_OF_STRING(glyph)) { 354 break; 355 } 356 357 CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); 358 359 // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage 360 if (cachedGlyph->mIsValid) { 361 int penX = x + positions[(glyphsCount << 1)]; 362 int penY = y + positions[(glyphsCount << 1) + 1]; 363 364 switch (align) { 365 case SkPaint::kRight_Align: 366 penX -= SkFixedToFloat(cachedGlyph->mAdvanceX); 367 penY -= SkFixedToFloat(cachedGlyph->mAdvanceY); 368 break; 369 case SkPaint::kCenter_Align: 370 penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1); 371 penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1); 372 default: 373 break; 374 } 375 376 (*this.*render)(cachedGlyph, penX, penY, 377 bitmap, bitmapW, bitmapH, bounds, positions); 378 } 379 380 glyphsCount++; 381 } 382 } 383} 384 385void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) { 386 glyph->mAdvanceX = skiaGlyph.fAdvanceX; 387 glyph->mAdvanceY = skiaGlyph.fAdvanceY; 388 glyph->mBitmapLeft = skiaGlyph.fLeft; 389 glyph->mBitmapTop = skiaGlyph.fTop; 390 glyph->mLsbDelta = skiaGlyph.fLsbDelta; 391 glyph->mRsbDelta = skiaGlyph.fRsbDelta; 392 393 uint32_t startX = 0; 394 uint32_t startY = 0; 395 396 // Get the bitmap for the glyph 397 paint->findImage(skiaGlyph); 398 mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY); 399 400 if (!glyph->mIsValid) { 401 return; 402 } 403 404 uint32_t endX = startX + skiaGlyph.fWidth; 405 uint32_t endY = startY + skiaGlyph.fHeight; 406 407 glyph->mStartX = startX; 408 glyph->mStartY = startY; 409 glyph->mBitmapWidth = skiaGlyph.fWidth; 410 glyph->mBitmapHeight = skiaGlyph.fHeight; 411 412 uint32_t cacheWidth = glyph->mCachedTextureLine->mCacheTexture->mWidth; 413 uint32_t cacheHeight = glyph->mCachedTextureLine->mCacheTexture->mHeight; 414 415 glyph->mBitmapMinU = (float) startX / (float) cacheWidth; 416 glyph->mBitmapMinV = (float) startY / (float) cacheHeight; 417 glyph->mBitmapMaxU = (float) endX / (float) cacheWidth; 418 glyph->mBitmapMaxV = (float) endY / (float) cacheHeight; 419 420 mState->mUploadTexture = true; 421} 422 423CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) { 424 CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); 425 mCachedGlyphs.add(glyph, newGlyph); 426 427 const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph); 428 newGlyph->mGlyphIndex = skiaGlyph.fID; 429 newGlyph->mIsValid = false; 430 431 updateGlyphCache(paint, skiaGlyph, newGlyph); 432 433 return newGlyph; 434} 435 436Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize, 437 int flags, uint32_t italicStyle, uint32_t scaleX, 438 SkPaint::Style style, uint32_t strokeWidth) { 439 Vector<Font*> &activeFonts = state->mActiveFonts; 440 441 for (uint32_t i = 0; i < activeFonts.size(); i++) { 442 Font* font = activeFonts[i]; 443 if (font->mFontId == fontId && font->mFontSize == fontSize && 444 font->mFlags == flags && font->mItalicStyle == italicStyle && 445 font->mScaleX == scaleX && font->mStyle == style && 446 (style == SkPaint::kFill_Style || font->mStrokeWidth == strokeWidth)) { 447 return font; 448 } 449 } 450 451 Font* newFont = new Font(state, fontId, fontSize, flags, italicStyle, 452 scaleX, style, strokeWidth); 453 activeFonts.push(newFont); 454 return newFont; 455} 456 457/////////////////////////////////////////////////////////////////////////////// 458// FontRenderer 459/////////////////////////////////////////////////////////////////////////////// 460 461static bool sLogFontRendererCreate = true; 462 463FontRenderer::FontRenderer() { 464 if (sLogFontRendererCreate) { 465 INIT_LOGD("Creating FontRenderer"); 466 } 467 468 mGammaTable = NULL; 469 mInitialized = false; 470 mMaxNumberOfQuads = 1024; 471 mCurrentQuadIndex = 0; 472 473 mTextMeshPtr = NULL; 474 mCurrentCacheTexture = NULL; 475 mLastCacheTexture = NULL; 476 mCacheTextureSmall = NULL; 477 mCacheTexture128 = NULL; 478 mCacheTexture256 = NULL; 479 mCacheTexture512 = NULL; 480 481 mLinearFiltering = false; 482 483 mIndexBufferID = 0; 484 485 mSmallCacheWidth = DEFAULT_TEXT_CACHE_WIDTH; 486 mSmallCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT; 487 488 char property[PROPERTY_VALUE_MAX]; 489 if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) { 490 if (sLogFontRendererCreate) { 491 INIT_LOGD(" Setting text cache width to %s pixels", property); 492 } 493 mSmallCacheWidth = atoi(property); 494 } else { 495 if (sLogFontRendererCreate) { 496 INIT_LOGD(" Using default text cache width of %i pixels", mSmallCacheWidth); 497 } 498 } 499 500 if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) { 501 if (sLogFontRendererCreate) { 502 INIT_LOGD(" Setting text cache width to %s pixels", property); 503 } 504 mSmallCacheHeight = atoi(property); 505 } else { 506 if (sLogFontRendererCreate) { 507 INIT_LOGD(" Using default text cache height of %i pixels", mSmallCacheHeight); 508 } 509 } 510 511 sLogFontRendererCreate = false; 512} 513 514FontRenderer::~FontRenderer() { 515 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 516 delete mCacheLines[i]; 517 } 518 mCacheLines.clear(); 519 520 if (mInitialized) { 521 // Unbinding the buffer shouldn't be necessary but it crashes with some drivers 522 Caches::getInstance().unbindIndicesBuffer(); 523 glDeleteBuffers(1, &mIndexBufferID); 524 525 delete[] mTextMeshPtr; 526 delete mCacheTextureSmall; 527 delete mCacheTexture128; 528 delete mCacheTexture256; 529 delete mCacheTexture512; 530 } 531 532 Vector<Font*> fontsToDereference = mActiveFonts; 533 for (uint32_t i = 0; i < fontsToDereference.size(); i++) { 534 delete fontsToDereference[i]; 535 } 536} 537 538void FontRenderer::flushAllAndInvalidate() { 539 if (mCurrentQuadIndex != 0) { 540 issueDrawCommand(); 541 mCurrentQuadIndex = 0; 542 } 543 for (uint32_t i = 0; i < mActiveFonts.size(); i++) { 544 mActiveFonts[i]->invalidateTextureCache(); 545 } 546 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 547 mCacheLines[i]->mCurrentCol = 0; 548 } 549} 550 551void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) { 552 if (cacheTexture && cacheTexture->mTexture) { 553 glDeleteTextures(1, &cacheTexture->mTextureId); 554 delete cacheTexture->mTexture; 555 cacheTexture->mTexture = NULL; 556 } 557} 558 559void FontRenderer::flushLargeCaches() { 560 if ((!mCacheTexture128 || !mCacheTexture128->mTexture) && 561 (!mCacheTexture256 || !mCacheTexture256->mTexture) && 562 (!mCacheTexture512 || !mCacheTexture512->mTexture)) { 563 // Typical case; no large glyph caches allocated 564 return; 565 } 566 567 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 568 CacheTextureLine* cacheLine = mCacheLines[i]; 569 if ((cacheLine->mCacheTexture == mCacheTexture128 || 570 cacheLine->mCacheTexture == mCacheTexture256 || 571 cacheLine->mCacheTexture == mCacheTexture512) && 572 cacheLine->mCacheTexture->mTexture != NULL) { 573 cacheLine->mCurrentCol = 0; 574 for (uint32_t i = 0; i < mActiveFonts.size(); i++) { 575 mActiveFonts[i]->invalidateTextureCache(cacheLine); 576 } 577 } 578 } 579 580 deallocateTextureMemory(mCacheTexture128); 581 deallocateTextureMemory(mCacheTexture256); 582 deallocateTextureMemory(mCacheTexture512); 583} 584 585void FontRenderer::allocateTextureMemory(CacheTexture *cacheTexture) { 586 int width = cacheTexture->mWidth; 587 int height = cacheTexture->mHeight; 588 cacheTexture->mTexture = new uint8_t[width * height]; 589 memset(cacheTexture->mTexture, 0, width * height * sizeof(uint8_t)); 590 glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId); 591 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 592 // Initialize texture dimensions 593 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, 594 GL_ALPHA, GL_UNSIGNED_BYTE, 0); 595 596 const GLenum filtering = cacheTexture->mLinearFiltering ? GL_LINEAR : GL_NEAREST; 597 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 598 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 599 600 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 601 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 602} 603 604void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, 605 uint32_t* retOriginX, uint32_t* retOriginY) { 606 cachedGlyph->mIsValid = false; 607 // If the glyph is too tall, don't cache it 608 if (glyph.fHeight + TEXTURE_BORDER_SIZE > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { 609 ALOGE("Font size to large to fit in cache. width, height = %i, %i", 610 (int) glyph.fWidth, (int) glyph.fHeight); 611 return; 612 } 613 614 // Now copy the bitmap into the cache texture 615 uint32_t startX = 0; 616 uint32_t startY = 0; 617 618 bool bitmapFit = false; 619 CacheTextureLine *cacheLine; 620 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 621 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); 622 if (bitmapFit) { 623 cacheLine = mCacheLines[i]; 624 break; 625 } 626 } 627 628 // If the new glyph didn't fit, flush the state so far and invalidate everything 629 if (!bitmapFit) { 630 flushAllAndInvalidate(); 631 632 // Try to fit it again 633 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 634 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); 635 if (bitmapFit) { 636 cacheLine = mCacheLines[i]; 637 break; 638 } 639 } 640 641 // if we still don't fit, something is wrong and we shouldn't draw 642 if (!bitmapFit) { 643 return; 644 } 645 } 646 647 cachedGlyph->mCachedTextureLine = cacheLine; 648 649 *retOriginX = startX; 650 *retOriginY = startY; 651 652 uint32_t endX = startX + glyph.fWidth; 653 uint32_t endY = startY + glyph.fHeight; 654 655 uint32_t cacheWidth = cacheLine->mMaxWidth; 656 657 CacheTexture *cacheTexture = cacheLine->mCacheTexture; 658 if (cacheTexture->mTexture == NULL) { 659 // Large-glyph texture memory is allocated only as needed 660 allocateTextureMemory(cacheTexture); 661 } 662 uint8_t* cacheBuffer = cacheTexture->mTexture; 663 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 664 unsigned int stride = glyph.rowBytes(); 665 666 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 667 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 668 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) { 669 uint8_t tempCol = bitmapBuffer[bY * stride + bX]; 670 cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol]; 671 } 672 } 673 674 cachedGlyph->mIsValid = true; 675} 676 677CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { 678 GLuint textureId; 679 glGenTextures(1, &textureId); 680 uint8_t* textureMemory = NULL; 681 682 CacheTexture* cacheTexture = new CacheTexture(textureMemory, textureId, width, height); 683 if (allocate) { 684 allocateTextureMemory(cacheTexture); 685 } 686 return cacheTexture; 687} 688 689void FontRenderer::initTextTexture() { 690 mCacheLines.clear(); 691 692 // Next, use other, separate caches for large glyphs. 693 uint16_t maxWidth = 0; 694 if (Caches::hasInstance()) { 695 maxWidth = Caches::getInstance().maxTextureSize; 696 } 697 if (maxWidth > MAX_TEXT_CACHE_WIDTH || maxWidth == 0) { 698 maxWidth = MAX_TEXT_CACHE_WIDTH; 699 } 700 if (mCacheTextureSmall != NULL) { 701 delete mCacheTextureSmall; 702 delete mCacheTexture128; 703 delete mCacheTexture256; 704 delete mCacheTexture512; 705 } 706 mCacheTextureSmall = createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true); 707 mCacheTexture128 = createCacheTexture(maxWidth, 256, false); 708 mCacheTexture256 = createCacheTexture(maxWidth, 256, false); 709 mCacheTexture512 = createCacheTexture(maxWidth, 512, false); 710 mCurrentCacheTexture = mCacheTextureSmall; 711 712 mUploadTexture = false; 713 // Split up our default cache texture into lines of certain widths 714 int nextLine = 0; 715 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, 0, mCacheTextureSmall)); 716 nextLine += mCacheLines.top()->mMaxHeight; 717 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); 718 nextLine += mCacheLines.top()->mMaxHeight; 719 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); 720 nextLine += mCacheLines.top()->mMaxHeight; 721 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); 722 nextLine += mCacheLines.top()->mMaxHeight; 723 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); 724 nextLine += mCacheLines.top()->mMaxHeight; 725 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, 0, mCacheTextureSmall)); 726 nextLine += mCacheLines.top()->mMaxHeight; 727 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine, 728 nextLine, 0, mCacheTextureSmall)); 729 730 // The first cache is split into 2 lines of height 128, the rest have just one cache line. 731 mCacheLines.push(new CacheTextureLine(maxWidth, 128, 0, 0, mCacheTexture128)); 732 mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, 0, mCacheTexture128)); 733 mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, 0, mCacheTexture256)); 734 mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, 0, mCacheTexture512)); 735} 736 737// Avoid having to reallocate memory and render quad by quad 738void FontRenderer::initVertexArrayBuffers() { 739 uint32_t numIndices = mMaxNumberOfQuads * 6; 740 uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); 741 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); 742 743 // Four verts, two triangles , six indices per quad 744 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) { 745 int i6 = i * 6; 746 int i4 = i * 4; 747 748 indexBufferData[i6 + 0] = i4 + 0; 749 indexBufferData[i6 + 1] = i4 + 1; 750 indexBufferData[i6 + 2] = i4 + 2; 751 752 indexBufferData[i6 + 3] = i4 + 0; 753 indexBufferData[i6 + 4] = i4 + 2; 754 indexBufferData[i6 + 5] = i4 + 3; 755 } 756 757 glGenBuffers(1, &mIndexBufferID); 758 Caches::getInstance().bindIndicesBuffer(mIndexBufferID); 759 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); 760 761 free(indexBufferData); 762 763 uint32_t coordSize = 2; 764 uint32_t uvSize = 2; 765 uint32_t vertsPerQuad = 4; 766 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize; 767 mTextMeshPtr = new float[vertexBufferSize]; 768} 769 770// We don't want to allocate anything unless we actually draw text 771void FontRenderer::checkInit() { 772 if (mInitialized) { 773 return; 774 } 775 776 initTextTexture(); 777 initVertexArrayBuffers(); 778 779 mInitialized = true; 780} 781 782void FontRenderer::checkTextureUpdate() { 783 if (!mUploadTexture && mLastCacheTexture == mCurrentCacheTexture) { 784 return; 785 } 786 787 Caches& caches = Caches::getInstance(); 788 GLuint lastTextureId = 0; 789 // Iterate over all the cache lines and see which ones need to be updated 790 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 791 CacheTextureLine* cl = mCacheLines[i]; 792 if (cl->mDirty && cl->mCacheTexture->mTexture != NULL) { 793 CacheTexture* cacheTexture = cl->mCacheTexture; 794 uint32_t xOffset = 0; 795 uint32_t yOffset = cl->mCurrentRow; 796 uint32_t width = cl->mMaxWidth; 797 uint32_t height = cl->mMaxHeight; 798 void* textureData = cacheTexture->mTexture + (yOffset * width); 799 800 if (cacheTexture->mTextureId != lastTextureId) { 801 caches.activeTexture(0); 802 glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId); 803 lastTextureId = cacheTexture->mTextureId; 804 } 805 glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, 806 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 807 808 cl->mDirty = false; 809 } 810 } 811 812 glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->mTextureId); 813 if (mLinearFiltering != mCurrentCacheTexture->mLinearFiltering) { 814 const GLenum filtering = mLinearFiltering ? GL_LINEAR : GL_NEAREST; 815 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 816 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 817 mCurrentCacheTexture->mLinearFiltering = mLinearFiltering; 818 } 819 mLastCacheTexture = mCurrentCacheTexture; 820 821 mUploadTexture = false; 822} 823 824void FontRenderer::issueDrawCommand() { 825 checkTextureUpdate(); 826 827 Caches& caches = Caches::getInstance(); 828 caches.bindIndicesBuffer(mIndexBufferID); 829 if (!mDrawn) { 830 float* buffer = mTextMeshPtr; 831 int offset = 2; 832 833 bool force = caches.unbindMeshBuffer(); 834 caches.bindPositionVertexPointer(force, caches.currentProgram->position, buffer); 835 caches.bindTexCoordsVertexPointer(force, caches.currentProgram->texCoords, 836 buffer + offset); 837 } 838 839 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL); 840 841 mDrawn = true; 842} 843 844void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, 845 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 846 float x4, float y4, float u4, float v4, CacheTexture* texture) { 847 if (texture != mCurrentCacheTexture) { 848 if (mCurrentQuadIndex != 0) { 849 // First, draw everything stored already which uses the previous texture 850 issueDrawCommand(); 851 mCurrentQuadIndex = 0; 852 } 853 // Now use the new texture id 854 mCurrentCacheTexture = texture; 855 } 856 857 const uint32_t vertsPerQuad = 4; 858 const uint32_t floatsPerVert = 4; 859 float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert; 860 861 (*currentPos++) = x1; 862 (*currentPos++) = y1; 863 (*currentPos++) = u1; 864 (*currentPos++) = v1; 865 866 (*currentPos++) = x2; 867 (*currentPos++) = y2; 868 (*currentPos++) = u2; 869 (*currentPos++) = v2; 870 871 (*currentPos++) = x3; 872 (*currentPos++) = y3; 873 (*currentPos++) = u3; 874 (*currentPos++) = v3; 875 876 (*currentPos++) = x4; 877 (*currentPos++) = y4; 878 (*currentPos++) = u4; 879 (*currentPos++) = v4; 880 881 mCurrentQuadIndex++; 882} 883 884void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, 885 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 886 float x4, float y4, float u4, float v4, CacheTexture* texture) { 887 888 if (mClip && 889 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { 890 return; 891 } 892 893 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 894 895 if (mBounds) { 896 mBounds->left = fmin(mBounds->left, x1); 897 mBounds->top = fmin(mBounds->top, y3); 898 mBounds->right = fmax(mBounds->right, x3); 899 mBounds->bottom = fmax(mBounds->bottom, y1); 900 } 901 902 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 903 issueDrawCommand(); 904 mCurrentQuadIndex = 0; 905 } 906} 907 908void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, 909 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 910 float x4, float y4, float u4, float v4, CacheTexture* texture) { 911 912 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 913 914 if (mBounds) { 915 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4)))); 916 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); 917 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); 918 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); 919 } 920 921 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 922 issueDrawCommand(); 923 mCurrentQuadIndex = 0; 924 } 925} 926 927uint32_t FontRenderer::getRemainingCacheCapacity() { 928 uint32_t remainingCapacity = 0; 929 float totalPixels = 0; 930 for(uint32_t i = 0; i < mCacheLines.size(); i ++) { 931 remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol); 932 totalPixels += mCacheLines[i]->mMaxWidth; 933 } 934 remainingCapacity = (remainingCapacity * 100) / totalPixels; 935 return remainingCapacity; 936} 937 938void FontRenderer::precacheLatin(SkPaint* paint) { 939 // Remaining capacity is measured in % 940 uint32_t remainingCapacity = getRemainingCacheCapacity(); 941 uint32_t precacheIndex = 0; 942 943 // We store a string with letters in a rough frequency of occurrence 944 String16 l("eisarntolcdugpmhbyfvkwzxjq EISARNTOLCDUGPMHBYFVKWZXJQ,.?!()-+@;:'0123456789"); 945 946 size_t size = l.size(); 947 uint16_t latin[size]; 948 paint->utfToGlyphs(l.string(), SkPaint::kUTF16_TextEncoding, size * sizeof(char16_t), latin); 949 950 while (remainingCapacity > 25 && precacheIndex < size) { 951 mCurrentFont->getCachedGlyph(paint, TO_GLYPH(latin[precacheIndex])); 952 remainingCapacity = getRemainingCacheCapacity(); 953 precacheIndex++; 954 } 955} 956 957void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { 958 uint32_t currentNumFonts = mActiveFonts.size(); 959 int flags = 0; 960 if (paint->isFakeBoldText()) { 961 flags |= Font::kFakeBold; 962 } 963 964 const float skewX = paint->getTextSkewX(); 965 uint32_t italicStyle = *(uint32_t*) &skewX; 966 const float scaleXFloat = paint->getTextScaleX(); 967 uint32_t scaleX = *(uint32_t*) &scaleXFloat; 968 SkPaint::Style style = paint->getStyle(); 969 const float strokeWidthFloat = paint->getStrokeWidth(); 970 uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat; 971 mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle, 972 scaleX, style, strokeWidth); 973 974 const float maxPrecacheFontSize = 40.0f; 975 bool isNewFont = currentNumFonts != mActiveFonts.size(); 976 977 if (isNewFont && fontSize <= maxPrecacheFontSize) { 978 precacheLatin(paint); 979 } 980} 981 982FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, 983 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) { 984 checkInit(); 985 986 if (!mCurrentFont) { 987 DropShadow image; 988 image.width = 0; 989 image.height = 0; 990 image.image = NULL; 991 image.penX = 0; 992 image.penY = 0; 993 return image; 994 } 995 996 mDrawn = false; 997 mClip = NULL; 998 mBounds = NULL; 999 1000 Rect bounds; 1001 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds); 1002 1003 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; 1004 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; 1005 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight]; 1006 1007 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) { 1008 dataBuffer[i] = 0; 1009 } 1010 1011 int penX = radius - bounds.left; 1012 int penY = radius - bounds.bottom; 1013 1014 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, 1015 dataBuffer, paddedWidth, paddedHeight); 1016 blurImage(dataBuffer, paddedWidth, paddedHeight, radius); 1017 1018 DropShadow image; 1019 image.width = paddedWidth; 1020 image.height = paddedHeight; 1021 image.image = dataBuffer; 1022 image.penX = penX; 1023 image.penY = penY; 1024 1025 return image; 1026} 1027 1028void FontRenderer::initRender(const Rect* clip, Rect* bounds) { 1029 checkInit(); 1030 1031 mDrawn = false; 1032 mBounds = bounds; 1033 mClip = clip; 1034} 1035 1036void FontRenderer::finishRender() { 1037 mBounds = NULL; 1038 mClip = NULL; 1039 1040 if (mCurrentQuadIndex != 0) { 1041 issueDrawCommand(); 1042 mCurrentQuadIndex = 0; 1043 } 1044} 1045 1046bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text, 1047 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) { 1048 if (!mCurrentFont) { 1049 ALOGE("No font set"); 1050 return false; 1051 } 1052 1053 initRender(clip, bounds); 1054 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y); 1055 finishRender(); 1056 1057 return mDrawn; 1058} 1059 1060bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, 1061 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, 1062 const float* positions, Rect* bounds) { 1063 if (!mCurrentFont) { 1064 ALOGE("No font set"); 1065 return false; 1066 } 1067 1068 initRender(clip, bounds); 1069 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); 1070 finishRender(); 1071 1072 return mDrawn; 1073} 1074 1075bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, 1076 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, 1077 float hOffset, float vOffset, Rect* bounds) { 1078 if (!mCurrentFont) { 1079 ALOGE("No font set"); 1080 return false; 1081 } 1082 1083 initRender(clip, bounds); 1084 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); 1085 finishRender(); 1086 1087 return mDrawn; 1088} 1089 1090void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) { 1091 // Compute gaussian weights for the blur 1092 // e is the euler's number 1093 float e = 2.718281828459045f; 1094 float pi = 3.1415926535897932f; 1095 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 ) 1096 // x is of the form [-radius .. 0 .. radius] 1097 // and sigma varies with radius. 1098 // Based on some experimental radius values and sigma's 1099 // we approximately fit sigma = f(radius) as 1100 // sigma = radius * 0.3 + 0.6 1101 // The larger the radius gets, the more our gaussian blur 1102 // will resemble a box blur since with large sigma 1103 // the gaussian curve begins to lose its shape 1104 float sigma = 0.3f * (float) radius + 0.6f; 1105 1106 // Now compute the coefficints 1107 // We will store some redundant values to save some math during 1108 // the blur calculations 1109 // precompute some values 1110 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma); 1111 float coeff2 = - 1.0f / (2.0f * sigma * sigma); 1112 1113 float normalizeFactor = 0.0f; 1114 for (int32_t r = -radius; r <= radius; r ++) { 1115 float floatR = (float) r; 1116 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2); 1117 normalizeFactor += weights[r + radius]; 1118 } 1119 1120 //Now we need to normalize the weights because all our coefficients need to add up to one 1121 normalizeFactor = 1.0f / normalizeFactor; 1122 for (int32_t r = -radius; r <= radius; r ++) { 1123 weights[r + radius] *= normalizeFactor; 1124 } 1125} 1126 1127void FontRenderer::horizontalBlur(float* weights, int32_t radius, 1128 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 1129 float blurredPixel = 0.0f; 1130 float currentPixel = 0.0f; 1131 1132 for (int32_t y = 0; y < height; y ++) { 1133 1134 const uint8_t* input = source + y * width; 1135 uint8_t* output = dest + y * width; 1136 1137 for (int32_t x = 0; x < width; x ++) { 1138 blurredPixel = 0.0f; 1139 const float* gPtr = weights; 1140 // Optimization for non-border pixels 1141 if (x > radius && x < (width - radius)) { 1142 const uint8_t *i = input + (x - radius); 1143 for (int r = -radius; r <= radius; r ++) { 1144 currentPixel = (float) (*i); 1145 blurredPixel += currentPixel * gPtr[0]; 1146 gPtr++; 1147 i++; 1148 } 1149 } else { 1150 for (int32_t r = -radius; r <= radius; r ++) { 1151 // Stepping left and right away from the pixel 1152 int validW = x + r; 1153 if (validW < 0) { 1154 validW = 0; 1155 } 1156 if (validW > width - 1) { 1157 validW = width - 1; 1158 } 1159 1160 currentPixel = (float) input[validW]; 1161 blurredPixel += currentPixel * gPtr[0]; 1162 gPtr++; 1163 } 1164 } 1165 *output = (uint8_t)blurredPixel; 1166 output ++; 1167 } 1168 } 1169} 1170 1171void FontRenderer::verticalBlur(float* weights, int32_t radius, 1172 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 1173 float blurredPixel = 0.0f; 1174 float currentPixel = 0.0f; 1175 1176 for (int32_t y = 0; y < height; y ++) { 1177 1178 uint8_t* output = dest + y * width; 1179 1180 for (int32_t x = 0; x < width; x ++) { 1181 blurredPixel = 0.0f; 1182 const float* gPtr = weights; 1183 const uint8_t* input = source + x; 1184 // Optimization for non-border pixels 1185 if (y > radius && y < (height - radius)) { 1186 const uint8_t *i = input + ((y - radius) * width); 1187 for (int32_t r = -radius; r <= radius; r ++) { 1188 currentPixel = (float)(*i); 1189 blurredPixel += currentPixel * gPtr[0]; 1190 gPtr++; 1191 i += width; 1192 } 1193 } else { 1194 for (int32_t r = -radius; r <= radius; r ++) { 1195 int validH = y + r; 1196 // Clamp to zero and width 1197 if (validH < 0) { 1198 validH = 0; 1199 } 1200 if (validH > height - 1) { 1201 validH = height - 1; 1202 } 1203 1204 const uint8_t *i = input + validH * width; 1205 currentPixel = (float) (*i); 1206 blurredPixel += currentPixel * gPtr[0]; 1207 gPtr++; 1208 } 1209 } 1210 *output = (uint8_t) blurredPixel; 1211 output ++; 1212 } 1213 } 1214} 1215 1216 1217void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) { 1218 float *gaussian = new float[2 * radius + 1]; 1219 computeGaussianWeights(gaussian, radius); 1220 1221 uint8_t* scratch = new uint8_t[width * height]; 1222 1223 horizontalBlur(gaussian, radius, image, scratch, width, height); 1224 verticalBlur(gaussian, radius, scratch, image, width, height); 1225 1226 delete[] gaussian; 1227 delete[] scratch; 1228} 1229 1230}; // namespace uirenderer 1231}; // namespace android 1232