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