FontRenderer.cpp revision dd7c8e4c68205d39109d4317dd0c9b05ed43e8e5
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 delete[] mTextMeshPtr; 522 delete mCacheTextureSmall; 523 delete mCacheTexture128; 524 delete mCacheTexture256; 525 delete mCacheTexture512; 526 } 527 528 Vector<Font*> fontsToDereference = mActiveFonts; 529 for (uint32_t i = 0; i < fontsToDereference.size(); i++) { 530 delete fontsToDereference[i]; 531 } 532} 533 534void FontRenderer::flushAllAndInvalidate() { 535 if (mCurrentQuadIndex != 0) { 536 issueDrawCommand(); 537 mCurrentQuadIndex = 0; 538 } 539 for (uint32_t i = 0; i < mActiveFonts.size(); i++) { 540 mActiveFonts[i]->invalidateTextureCache(); 541 } 542 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 543 mCacheLines[i]->mCurrentCol = 0; 544 } 545} 546 547void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) { 548 if (cacheTexture && cacheTexture->mTexture) { 549 glDeleteTextures(1, &cacheTexture->mTextureId); 550 delete cacheTexture->mTexture; 551 cacheTexture->mTexture = NULL; 552 } 553} 554 555void FontRenderer::flushLargeCaches() { 556 if ((!mCacheTexture128 || !mCacheTexture128->mTexture) && 557 (!mCacheTexture256 || !mCacheTexture256->mTexture) && 558 (!mCacheTexture512 || !mCacheTexture512->mTexture)) { 559 // Typical case; no large glyph caches allocated 560 return; 561 } 562 563 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 564 CacheTextureLine* cacheLine = mCacheLines[i]; 565 if ((cacheLine->mCacheTexture == mCacheTexture128 || 566 cacheLine->mCacheTexture == mCacheTexture256 || 567 cacheLine->mCacheTexture == mCacheTexture512) && 568 cacheLine->mCacheTexture->mTexture != NULL) { 569 cacheLine->mCurrentCol = 0; 570 for (uint32_t i = 0; i < mActiveFonts.size(); i++) { 571 mActiveFonts[i]->invalidateTextureCache(cacheLine); 572 } 573 } 574 } 575 576 deallocateTextureMemory(mCacheTexture128); 577 deallocateTextureMemory(mCacheTexture256); 578 deallocateTextureMemory(mCacheTexture512); 579} 580 581void FontRenderer::allocateTextureMemory(CacheTexture *cacheTexture) { 582 int width = cacheTexture->mWidth; 583 int height = cacheTexture->mHeight; 584 cacheTexture->mTexture = new uint8_t[width * height]; 585 memset(cacheTexture->mTexture, 0, width * height * sizeof(uint8_t)); 586 glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId); 587 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 588 // Initialize texture dimensions 589 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, 590 GL_ALPHA, GL_UNSIGNED_BYTE, 0); 591 592 const GLenum filtering = cacheTexture->mLinearFiltering ? GL_LINEAR : GL_NEAREST; 593 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 594 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 595 596 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 597 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 598} 599 600void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, 601 uint32_t* retOriginX, uint32_t* retOriginY) { 602 cachedGlyph->mIsValid = false; 603 // If the glyph is too tall, don't cache it 604 if (glyph.fHeight + TEXTURE_BORDER_SIZE > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { 605 ALOGE("Font size to large to fit in cache. width, height = %i, %i", 606 (int) glyph.fWidth, (int) glyph.fHeight); 607 return; 608 } 609 610 // Now copy the bitmap into the cache texture 611 uint32_t startX = 0; 612 uint32_t startY = 0; 613 614 bool bitmapFit = false; 615 CacheTextureLine *cacheLine; 616 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 617 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); 618 if (bitmapFit) { 619 cacheLine = mCacheLines[i]; 620 break; 621 } 622 } 623 624 // If the new glyph didn't fit, flush the state so far and invalidate everything 625 if (!bitmapFit) { 626 flushAllAndInvalidate(); 627 628 // Try to fit it again 629 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 630 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); 631 if (bitmapFit) { 632 cacheLine = mCacheLines[i]; 633 break; 634 } 635 } 636 637 // if we still don't fit, something is wrong and we shouldn't draw 638 if (!bitmapFit) { 639 return; 640 } 641 } 642 643 cachedGlyph->mCachedTextureLine = cacheLine; 644 645 *retOriginX = startX; 646 *retOriginY = startY; 647 648 uint32_t endX = startX + glyph.fWidth; 649 uint32_t endY = startY + glyph.fHeight; 650 651 uint32_t cacheWidth = cacheLine->mMaxWidth; 652 653 CacheTexture *cacheTexture = cacheLine->mCacheTexture; 654 if (cacheTexture->mTexture == NULL) { 655 // Large-glyph texture memory is allocated only as needed 656 allocateTextureMemory(cacheTexture); 657 } 658 uint8_t* cacheBuffer = cacheTexture->mTexture; 659 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 660 unsigned int stride = glyph.rowBytes(); 661 662 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 663 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 664 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) { 665 uint8_t tempCol = bitmapBuffer[bY * stride + bX]; 666 cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol]; 667 } 668 } 669 670 cachedGlyph->mIsValid = true; 671} 672 673CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { 674 GLuint textureId; 675 glGenTextures(1, &textureId); 676 uint8_t* textureMemory = NULL; 677 678 CacheTexture* cacheTexture = new CacheTexture(textureMemory, textureId, width, height); 679 if (allocate) { 680 allocateTextureMemory(cacheTexture); 681 } 682 return cacheTexture; 683} 684 685void FontRenderer::initTextTexture() { 686 mCacheLines.clear(); 687 688 // Next, use other, separate caches for large glyphs. 689 uint16_t maxWidth = 0; 690 if (Caches::hasInstance()) { 691 maxWidth = Caches::getInstance().maxTextureSize; 692 } 693 if (maxWidth > MAX_TEXT_CACHE_WIDTH || maxWidth == 0) { 694 maxWidth = MAX_TEXT_CACHE_WIDTH; 695 } 696 if (mCacheTextureSmall != NULL) { 697 delete mCacheTextureSmall; 698 delete mCacheTexture128; 699 delete mCacheTexture256; 700 delete mCacheTexture512; 701 } 702 mCacheTextureSmall = createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true); 703 mCacheTexture128 = createCacheTexture(maxWidth, 256, false); 704 mCacheTexture256 = createCacheTexture(maxWidth, 256, false); 705 mCacheTexture512 = createCacheTexture(maxWidth, 512, false); 706 mCurrentCacheTexture = mCacheTextureSmall; 707 708 mUploadTexture = false; 709 // Split up our default cache texture into lines of certain widths 710 int nextLine = 0; 711 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, 0, mCacheTextureSmall)); 712 nextLine += mCacheLines.top()->mMaxHeight; 713 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); 714 nextLine += mCacheLines.top()->mMaxHeight; 715 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); 716 nextLine += mCacheLines.top()->mMaxHeight; 717 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); 718 nextLine += mCacheLines.top()->mMaxHeight; 719 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); 720 nextLine += mCacheLines.top()->mMaxHeight; 721 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, 0, mCacheTextureSmall)); 722 nextLine += mCacheLines.top()->mMaxHeight; 723 mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine, 724 nextLine, 0, mCacheTextureSmall)); 725 726 // The first cache is split into 2 lines of height 128, the rest have just one cache line. 727 mCacheLines.push(new CacheTextureLine(maxWidth, 128, 0, 0, mCacheTexture128)); 728 mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, 0, mCacheTexture128)); 729 mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, 0, mCacheTexture256)); 730 mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, 0, mCacheTexture512)); 731} 732 733// Avoid having to reallocate memory and render quad by quad 734void FontRenderer::initVertexArrayBuffers() { 735 uint32_t numIndices = mMaxNumberOfQuads * 6; 736 uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); 737 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); 738 739 // Four verts, two triangles , six indices per quad 740 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) { 741 int i6 = i * 6; 742 int i4 = i * 4; 743 744 indexBufferData[i6 + 0] = i4 + 0; 745 indexBufferData[i6 + 1] = i4 + 1; 746 indexBufferData[i6 + 2] = i4 + 2; 747 748 indexBufferData[i6 + 3] = i4 + 0; 749 indexBufferData[i6 + 4] = i4 + 2; 750 indexBufferData[i6 + 5] = i4 + 3; 751 } 752 753 glGenBuffers(1, &mIndexBufferID); 754 Caches::getInstance().bindIndicesBuffer(mIndexBufferID); 755 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); 756 757 free(indexBufferData); 758 759 uint32_t coordSize = 2; 760 uint32_t uvSize = 2; 761 uint32_t vertsPerQuad = 4; 762 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize; 763 mTextMeshPtr = new float[vertexBufferSize]; 764} 765 766// We don't want to allocate anything unless we actually draw text 767void FontRenderer::checkInit() { 768 if (mInitialized) { 769 return; 770 } 771 772 initTextTexture(); 773 initVertexArrayBuffers(); 774 775 // We store a string with letters in a rough frequency of occurrence 776 mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq "); 777 mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ"); 778 mLatinPrecache += String16(",.?!()-+@;:`'"); 779 mLatinPrecache += String16("0123456789"); 780 781 mInitialized = true; 782} 783 784void FontRenderer::checkTextureUpdate() { 785 if (!mUploadTexture && mLastCacheTexture == mCurrentCacheTexture) { 786 return; 787 } 788 789 Caches& caches = Caches::getInstance(); 790 GLuint lastTextureId = 0; 791 // Iterate over all the cache lines and see which ones need to be updated 792 for (uint32_t i = 0; i < mCacheLines.size(); i++) { 793 CacheTextureLine* cl = mCacheLines[i]; 794 if (cl->mDirty && cl->mCacheTexture->mTexture != NULL) { 795 CacheTexture* cacheTexture = cl->mCacheTexture; 796 uint32_t xOffset = 0; 797 uint32_t yOffset = cl->mCurrentRow; 798 uint32_t width = cl->mMaxWidth; 799 uint32_t height = cl->mMaxHeight; 800 void* textureData = cacheTexture->mTexture + (yOffset * width); 801 802 if (cacheTexture->mTextureId != lastTextureId) { 803 caches.activeTexture(0); 804 glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId); 805 lastTextureId = cacheTexture->mTextureId; 806 } 807 glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, 808 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 809 810 cl->mDirty = false; 811 } 812 } 813 814 glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->mTextureId); 815 if (mLinearFiltering != mCurrentCacheTexture->mLinearFiltering) { 816 const GLenum filtering = mLinearFiltering ? GL_LINEAR : GL_NEAREST; 817 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 818 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 819 mCurrentCacheTexture->mLinearFiltering = mLinearFiltering; 820 } 821 mLastCacheTexture = mCurrentCacheTexture; 822 823 mUploadTexture = false; 824} 825 826void FontRenderer::issueDrawCommand() { 827 checkTextureUpdate(); 828 829 Caches& caches = Caches::getInstance(); 830 caches.bindIndicesBuffer(mIndexBufferID); 831 if (!mDrawn) { 832 float* buffer = mTextMeshPtr; 833 int offset = 2; 834 835 bool force = caches.unbindMeshBuffer(); 836 caches.bindPositionVertexPointer(force, caches.currentProgram->position, buffer); 837 caches.bindTexCoordsVertexPointer(force, caches.currentProgram->texCoords, 838 buffer + offset); 839 } 840 841 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL); 842 843 mDrawn = true; 844} 845 846void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, 847 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 848 float x4, float y4, float u4, float v4, CacheTexture* texture) { 849 if (texture != mCurrentCacheTexture) { 850 if (mCurrentQuadIndex != 0) { 851 // First, draw everything stored already which uses the previous texture 852 issueDrawCommand(); 853 mCurrentQuadIndex = 0; 854 } 855 // Now use the new texture id 856 mCurrentCacheTexture = texture; 857 } 858 859 const uint32_t vertsPerQuad = 4; 860 const uint32_t floatsPerVert = 4; 861 float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert; 862 863 (*currentPos++) = x1; 864 (*currentPos++) = y1; 865 (*currentPos++) = u1; 866 (*currentPos++) = v1; 867 868 (*currentPos++) = x2; 869 (*currentPos++) = y2; 870 (*currentPos++) = u2; 871 (*currentPos++) = v2; 872 873 (*currentPos++) = x3; 874 (*currentPos++) = y3; 875 (*currentPos++) = u3; 876 (*currentPos++) = v3; 877 878 (*currentPos++) = x4; 879 (*currentPos++) = y4; 880 (*currentPos++) = u4; 881 (*currentPos++) = v4; 882 883 mCurrentQuadIndex++; 884} 885 886void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, 887 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 888 float x4, float y4, float u4, float v4, CacheTexture* texture) { 889 890 if (mClip && 891 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { 892 return; 893 } 894 895 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 896 897 if (mBounds) { 898 mBounds->left = fmin(mBounds->left, x1); 899 mBounds->top = fmin(mBounds->top, y3); 900 mBounds->right = fmax(mBounds->right, x3); 901 mBounds->bottom = fmax(mBounds->bottom, y1); 902 } 903 904 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 905 issueDrawCommand(); 906 mCurrentQuadIndex = 0; 907 } 908} 909 910void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, 911 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 912 float x4, float y4, float u4, float v4, CacheTexture* texture) { 913 914 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 915 916 if (mBounds) { 917 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4)))); 918 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); 919 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); 920 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); 921 } 922 923 if (mCurrentQuadIndex == mMaxNumberOfQuads) { 924 issueDrawCommand(); 925 mCurrentQuadIndex = 0; 926 } 927} 928 929uint32_t FontRenderer::getRemainingCacheCapacity() { 930 uint32_t remainingCapacity = 0; 931 float totalPixels = 0; 932 for(uint32_t i = 0; i < mCacheLines.size(); i ++) { 933 remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol); 934 totalPixels += mCacheLines[i]->mMaxWidth; 935 } 936 remainingCapacity = (remainingCapacity * 100) / totalPixels; 937 return remainingCapacity; 938} 939 940void FontRenderer::precacheLatin(SkPaint* paint) { 941 // Remaining capacity is measured in % 942 uint32_t remainingCapacity = getRemainingCacheCapacity(); 943 uint32_t precacheIdx = 0; 944 while (remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) { 945 mCurrentFont->getCachedGlyph(paint, (int32_t) mLatinPrecache[precacheIdx]); 946 remainingCapacity = getRemainingCacheCapacity(); 947 precacheIdx ++; 948 } 949} 950 951void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { 952 uint32_t currentNumFonts = mActiveFonts.size(); 953 int flags = 0; 954 if (paint->isFakeBoldText()) { 955 flags |= Font::kFakeBold; 956 } 957 958 const float skewX = paint->getTextSkewX(); 959 uint32_t italicStyle = *(uint32_t*) &skewX; 960 const float scaleXFloat = paint->getTextScaleX(); 961 uint32_t scaleX = *(uint32_t*) &scaleXFloat; 962 SkPaint::Style style = paint->getStyle(); 963 const float strokeWidthFloat = paint->getStrokeWidth(); 964 uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat; 965 mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle, 966 scaleX, style, strokeWidth); 967 968 const float maxPrecacheFontSize = 40.0f; 969 bool isNewFont = currentNumFonts != mActiveFonts.size(); 970 971 if (isNewFont && fontSize <= maxPrecacheFontSize) { 972 precacheLatin(paint); 973 } 974} 975 976FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, 977 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) { 978 checkInit(); 979 980 if (!mCurrentFont) { 981 DropShadow image; 982 image.width = 0; 983 image.height = 0; 984 image.image = NULL; 985 image.penX = 0; 986 image.penY = 0; 987 return image; 988 } 989 990 mDrawn = false; 991 mClip = NULL; 992 mBounds = NULL; 993 994 Rect bounds; 995 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds); 996 997 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; 998 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; 999 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight]; 1000 1001 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) { 1002 dataBuffer[i] = 0; 1003 } 1004 1005 int penX = radius - bounds.left; 1006 int penY = radius - bounds.bottom; 1007 1008 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, 1009 dataBuffer, paddedWidth, paddedHeight); 1010 blurImage(dataBuffer, paddedWidth, paddedHeight, radius); 1011 1012 DropShadow image; 1013 image.width = paddedWidth; 1014 image.height = paddedHeight; 1015 image.image = dataBuffer; 1016 image.penX = penX; 1017 image.penY = penY; 1018 1019 return image; 1020} 1021 1022void FontRenderer::initRender(const Rect* clip, Rect* bounds) { 1023 checkInit(); 1024 1025 mDrawn = false; 1026 mBounds = bounds; 1027 mClip = clip; 1028} 1029 1030void FontRenderer::finishRender() { 1031 mBounds = NULL; 1032 mClip = NULL; 1033 1034 if (mCurrentQuadIndex != 0) { 1035 issueDrawCommand(); 1036 mCurrentQuadIndex = 0; 1037 } 1038} 1039 1040bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text, 1041 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) { 1042 if (!mCurrentFont) { 1043 ALOGE("No font set"); 1044 return false; 1045 } 1046 1047 initRender(clip, bounds); 1048 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y); 1049 finishRender(); 1050 1051 return mDrawn; 1052} 1053 1054bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, 1055 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, 1056 const float* positions, Rect* bounds) { 1057 if (!mCurrentFont) { 1058 ALOGE("No font set"); 1059 return false; 1060 } 1061 1062 initRender(clip, bounds); 1063 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); 1064 finishRender(); 1065 1066 return mDrawn; 1067} 1068 1069bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, 1070 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, 1071 float hOffset, float vOffset, Rect* bounds) { 1072 if (!mCurrentFont) { 1073 ALOGE("No font set"); 1074 return false; 1075 } 1076 1077 initRender(clip, bounds); 1078 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); 1079 finishRender(); 1080 1081 return mDrawn; 1082} 1083 1084void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) { 1085 // Compute gaussian weights for the blur 1086 // e is the euler's number 1087 float e = 2.718281828459045f; 1088 float pi = 3.1415926535897932f; 1089 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 ) 1090 // x is of the form [-radius .. 0 .. radius] 1091 // and sigma varies with radius. 1092 // Based on some experimental radius values and sigma's 1093 // we approximately fit sigma = f(radius) as 1094 // sigma = radius * 0.3 + 0.6 1095 // The larger the radius gets, the more our gaussian blur 1096 // will resemble a box blur since with large sigma 1097 // the gaussian curve begins to lose its shape 1098 float sigma = 0.3f * (float) radius + 0.6f; 1099 1100 // Now compute the coefficints 1101 // We will store some redundant values to save some math during 1102 // the blur calculations 1103 // precompute some values 1104 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma); 1105 float coeff2 = - 1.0f / (2.0f * sigma * sigma); 1106 1107 float normalizeFactor = 0.0f; 1108 for (int32_t r = -radius; r <= radius; r ++) { 1109 float floatR = (float) r; 1110 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2); 1111 normalizeFactor += weights[r + radius]; 1112 } 1113 1114 //Now we need to normalize the weights because all our coefficients need to add up to one 1115 normalizeFactor = 1.0f / normalizeFactor; 1116 for (int32_t r = -radius; r <= radius; r ++) { 1117 weights[r + radius] *= normalizeFactor; 1118 } 1119} 1120 1121void FontRenderer::horizontalBlur(float* weights, int32_t radius, 1122 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 1123 float blurredPixel = 0.0f; 1124 float currentPixel = 0.0f; 1125 1126 for (int32_t y = 0; y < height; y ++) { 1127 1128 const uint8_t* input = source + y * width; 1129 uint8_t* output = dest + y * width; 1130 1131 for (int32_t x = 0; x < width; x ++) { 1132 blurredPixel = 0.0f; 1133 const float* gPtr = weights; 1134 // Optimization for non-border pixels 1135 if (x > radius && x < (width - radius)) { 1136 const uint8_t *i = input + (x - radius); 1137 for (int r = -radius; r <= radius; r ++) { 1138 currentPixel = (float) (*i); 1139 blurredPixel += currentPixel * gPtr[0]; 1140 gPtr++; 1141 i++; 1142 } 1143 } else { 1144 for (int32_t r = -radius; r <= radius; r ++) { 1145 // Stepping left and right away from the pixel 1146 int validW = x + r; 1147 if (validW < 0) { 1148 validW = 0; 1149 } 1150 if (validW > width - 1) { 1151 validW = width - 1; 1152 } 1153 1154 currentPixel = (float) input[validW]; 1155 blurredPixel += currentPixel * gPtr[0]; 1156 gPtr++; 1157 } 1158 } 1159 *output = (uint8_t)blurredPixel; 1160 output ++; 1161 } 1162 } 1163} 1164 1165void FontRenderer::verticalBlur(float* weights, int32_t radius, 1166 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) { 1167 float blurredPixel = 0.0f; 1168 float currentPixel = 0.0f; 1169 1170 for (int32_t y = 0; y < height; y ++) { 1171 1172 uint8_t* output = dest + y * width; 1173 1174 for (int32_t x = 0; x < width; x ++) { 1175 blurredPixel = 0.0f; 1176 const float* gPtr = weights; 1177 const uint8_t* input = source + x; 1178 // Optimization for non-border pixels 1179 if (y > radius && y < (height - radius)) { 1180 const uint8_t *i = input + ((y - radius) * width); 1181 for (int32_t r = -radius; r <= radius; r ++) { 1182 currentPixel = (float)(*i); 1183 blurredPixel += currentPixel * gPtr[0]; 1184 gPtr++; 1185 i += width; 1186 } 1187 } else { 1188 for (int32_t r = -radius; r <= radius; r ++) { 1189 int validH = y + r; 1190 // Clamp to zero and width 1191 if (validH < 0) { 1192 validH = 0; 1193 } 1194 if (validH > height - 1) { 1195 validH = height - 1; 1196 } 1197 1198 const uint8_t *i = input + validH * width; 1199 currentPixel = (float) (*i); 1200 blurredPixel += currentPixel * gPtr[0]; 1201 gPtr++; 1202 } 1203 } 1204 *output = (uint8_t) blurredPixel; 1205 output ++; 1206 } 1207 } 1208} 1209 1210 1211void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) { 1212 float *gaussian = new float[2 * radius + 1]; 1213 computeGaussianWeights(gaussian, radius); 1214 1215 uint8_t* scratch = new uint8_t[width * height]; 1216 1217 horizontalBlur(gaussian, radius, image, scratch, width, height); 1218 verticalBlur(gaussian, radius, scratch, image, width, height); 1219 1220 delete[] gaussian; 1221 delete[] scratch; 1222} 1223 1224}; // namespace uirenderer 1225}; // namespace android 1226