FontRenderer.cpp revision f7648b7f24bc4cf8a66df4c0cfb5e8aa2b7d3ac8
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "OpenGLRenderer" 18 19#include <SkGlyph.h> 20#include <SkUtils.h> 21 22#include <cutils/properties.h> 23 24#include <utils/Functor.h> 25#include <utils/Log.h> 26 27#ifdef ANDROID_ENABLE_RENDERSCRIPT 28#include <RenderScript.h> 29#endif 30 31#include "utils/Blur.h" 32#include "utils/Timing.h" 33 34#include "Caches.h" 35#include "Debug.h" 36#include "Extensions.h" 37#include "FontRenderer.h" 38#include "Rect.h" 39 40namespace android { 41namespace uirenderer { 42 43// blur inputs smaller than this constant will bypass renderscript 44#define RS_MIN_INPUT_CUTOFF 10000 45 46/////////////////////////////////////////////////////////////////////////////// 47// FontRenderer 48/////////////////////////////////////////////////////////////////////////////// 49 50static bool sLogFontRendererCreate = true; 51 52FontRenderer::FontRenderer() : 53 mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) { 54 55 if (sLogFontRendererCreate) { 56 INIT_LOGD("Creating FontRenderer"); 57 } 58 59 mGammaTable = NULL; 60 mInitialized = false; 61 mMaxNumberOfQuads = 1024; 62 63 mCurrentCacheTexture = NULL; 64 65 mLinearFiltering = false; 66 67 mIndexBufferID = 0; 68 69 mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH; 70 mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT; 71 mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH; 72 mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT; 73 74 char property[PROPERTY_VALUE_MAX]; 75 if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) { 76 mSmallCacheWidth = atoi(property); 77 } 78 79 if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) { 80 mSmallCacheHeight = atoi(property); 81 } 82 83 if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) { 84 mLargeCacheWidth = atoi(property); 85 } 86 87 if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) { 88 mLargeCacheHeight = atoi(property); 89 } 90 91 uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize; 92 mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth; 93 mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight; 94 mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth; 95 mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight; 96 97 if (sLogFontRendererCreate) { 98 INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", 99 mSmallCacheWidth, mSmallCacheHeight, 100 mLargeCacheWidth, mLargeCacheHeight >> 1, 101 mLargeCacheWidth, mLargeCacheHeight >> 1, 102 mLargeCacheWidth, mLargeCacheHeight); 103 } 104 105 sLogFontRendererCreate = false; 106} 107 108FontRenderer::~FontRenderer() { 109 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 110 delete mCacheTextures[i]; 111 } 112 mCacheTextures.clear(); 113 114 if (mInitialized) { 115 // Unbinding the buffer shouldn't be necessary but it crashes with some drivers 116 Caches::getInstance().unbindIndicesBuffer(); 117 glDeleteBuffers(1, &mIndexBufferID); 118 } 119 120 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 121 while (it.next()) { 122 delete it.value(); 123 } 124 mActiveFonts.clear(); 125} 126 127void FontRenderer::flushAllAndInvalidate() { 128 issueDrawCommand(); 129 130 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 131 while (it.next()) { 132 it.value()->invalidateTextureCache(); 133 } 134 135 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 136 mCacheTextures[i]->init(); 137 } 138 139#if DEBUG_FONT_RENDERER 140 uint16_t totalGlyphs = 0; 141 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 142 totalGlyphs += mCacheTextures[i]->getGlyphCount(); 143 // Erase caches, just as a debugging facility 144 if (mCacheTextures[i]->getTexture()) { 145 memset(mCacheTextures[i]->getTexture(), 0, 146 mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight()); 147 } 148 } 149 ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); 150#endif 151} 152 153void FontRenderer::flushLargeCaches() { 154 // Start from 1; don't deallocate smallest/default texture 155 for (uint32_t i = 1; i < mCacheTextures.size(); i++) { 156 CacheTexture* cacheTexture = mCacheTextures[i]; 157 if (cacheTexture->getTexture()) { 158 cacheTexture->init(); 159 LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); 160 while (it.next()) { 161 it.value()->invalidateTextureCache(cacheTexture); 162 } 163 cacheTexture->releaseTexture(); 164 } 165 } 166} 167 168CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, 169 uint32_t* startX, uint32_t* startY) { 170 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 171 if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { 172 return mCacheTextures[i]; 173 } 174 } 175 // Could not fit glyph into current cache textures 176 return NULL; 177} 178 179void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, 180 uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { 181 checkInit(); 182 183 // If the glyph bitmap is empty let's assum the glyph is valid 184 // so we can avoid doing extra work later on 185 if (glyph.fWidth == 0 || glyph.fHeight == 0) { 186 cachedGlyph->mIsValid = true; 187 cachedGlyph->mCacheTexture = NULL; 188 return; 189 } 190 191 cachedGlyph->mIsValid = false; 192 193 // If the glyph is too tall, don't cache it 194 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > 195 mCacheTextures[mCacheTextures.size() - 1]->getHeight()) { 196 ALOGE("Font size too large to fit in cache. width, height = %i, %i", 197 (int) glyph.fWidth, (int) glyph.fHeight); 198 return; 199 } 200 201 // Now copy the bitmap into the cache texture 202 uint32_t startX = 0; 203 uint32_t startY = 0; 204 205 CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); 206 207 if (!cacheTexture) { 208 if (!precaching) { 209 // If the new glyph didn't fit and we are not just trying to precache it, 210 // clear out the cache and try again 211 flushAllAndInvalidate(); 212 cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); 213 } 214 215 if (!cacheTexture) { 216 // either the glyph didn't fit or we're precaching and will cache it when we draw 217 return; 218 } 219 } 220 221 cachedGlyph->mCacheTexture = cacheTexture; 222 223 *retOriginX = startX; 224 *retOriginY = startY; 225 226 uint32_t endX = startX + glyph.fWidth; 227 uint32_t endY = startY + glyph.fHeight; 228 229 uint32_t cacheWidth = cacheTexture->getWidth(); 230 231 if (!cacheTexture->getTexture()) { 232 Caches::getInstance().activeTexture(0); 233 // Large-glyph texture memory is allocated only as needed 234 cacheTexture->allocateTexture(); 235 } 236 if (!cacheTexture->mesh()) { 237 cacheTexture->allocateMesh(); 238 } 239 240 // Tells us whether the glyphs is B&W (1 bit per pixel) 241 // or anti-aliased (8 bits per pixel) 242 SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); 243 244 uint8_t* cacheBuffer = cacheTexture->getTexture(); 245 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; 246 247 // Copy the glyph image, taking the mask format into account 248 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; 249 int stride = glyph.rowBytes(); 250 251 uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; 252 memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); 253 254 switch (format) { 255 case SkMask::kA8_Format: { 256 if (mGammaTable) { 257 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { 258 row = cacheY * cacheWidth; 259 cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; 260 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { 261 uint8_t tempCol = bitmapBuffer[bY + bX]; 262 cacheBuffer[row + cacheX] = mGammaTable[tempCol]; 263 } 264 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; 265 } 266 } else { 267 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { 268 row = cacheY * cacheWidth; 269 memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); 270 cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; 271 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; 272 } 273 } 274 break; 275 } 276 case SkMask::kBW_Format: { 277 static const uint8_t COLORS[2] = { 0, 255 }; 278 279 for (cacheY = startY; cacheY < endY; cacheY++) { 280 cacheX = startX; 281 int rowBytes = stride; 282 uint8_t* buffer = bitmapBuffer; 283 284 row = cacheY * cacheWidth; 285 cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; 286 while (--rowBytes >= 0) { 287 uint8_t b = *buffer++; 288 for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) { 289 cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1]; 290 } 291 } 292 cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; 293 294 bitmapBuffer += stride; 295 } 296 break; 297 } 298 default: 299 ALOGW("Unkown glyph format: 0x%x", format); 300 break; 301 } 302 303 row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; 304 memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); 305 306 cachedGlyph->mIsValid = true; 307} 308 309CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { 310 CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads); 311 312 if (allocate) { 313 Caches::getInstance().activeTexture(0); 314 cacheTexture->allocateTexture(); 315 cacheTexture->allocateMesh(); 316 } 317 318 return cacheTexture; 319} 320 321void FontRenderer::initTextTexture() { 322 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 323 delete mCacheTextures[i]; 324 } 325 mCacheTextures.clear(); 326 327 mUploadTexture = false; 328 mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); 329 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); 330 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); 331 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false)); 332 mCurrentCacheTexture = mCacheTextures[0]; 333} 334 335// Avoid having to reallocate memory and render quad by quad 336void FontRenderer::initVertexArrayBuffers() { 337 uint32_t numIndices = mMaxNumberOfQuads * 6; 338 uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); 339 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); 340 341 // Four verts, two triangles , six indices per quad 342 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) { 343 int i6 = i * 6; 344 int i4 = i * 4; 345 346 indexBufferData[i6 + 0] = i4 + 0; 347 indexBufferData[i6 + 1] = i4 + 1; 348 indexBufferData[i6 + 2] = i4 + 2; 349 350 indexBufferData[i6 + 3] = i4 + 0; 351 indexBufferData[i6 + 4] = i4 + 2; 352 indexBufferData[i6 + 5] = i4 + 3; 353 } 354 355 glGenBuffers(1, &mIndexBufferID); 356 Caches::getInstance().bindIndicesBuffer(mIndexBufferID); 357 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); 358 359 free(indexBufferData); 360} 361 362// We don't want to allocate anything unless we actually draw text 363void FontRenderer::checkInit() { 364 if (mInitialized) { 365 return; 366 } 367 368 initTextTexture(); 369 initVertexArrayBuffers(); 370 371 mInitialized = true; 372} 373 374void FontRenderer::checkTextureUpdate() { 375 if (!mUploadTexture) { 376 return; 377 } 378 379 Caches& caches = Caches::getInstance(); 380 GLuint lastTextureId = 0; 381 382 // OpenGL ES 3.0+ lets us specify the row length for unpack operations such 383 // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture. 384 // With OpenGL ES 2.0 we have to upload entire stripes instead. 385 const bool hasUnpackRowLength = Extensions::getInstance().getMajorGlVersion() >= 3; 386 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 387 388 // Iterate over all the cache textures and see which ones need to be updated 389 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 390 CacheTexture* cacheTexture = mCacheTextures[i]; 391 if (cacheTexture->isDirty() && cacheTexture->getTexture()) { 392 const Rect* dirtyRect = cacheTexture->getDirtyRect(); 393 uint32_t x = hasUnpackRowLength ? dirtyRect->left : 0; 394 uint32_t y = dirtyRect->top; 395 uint32_t width = cacheTexture->getWidth(); 396 uint32_t height = dirtyRect->getHeight(); 397 void* textureData = cacheTexture->getTexture() + y * width + x; 398 399 if (cacheTexture->getTextureId() != lastTextureId) { 400 lastTextureId = cacheTexture->getTextureId(); 401 caches.activeTexture(0); 402 glBindTexture(GL_TEXTURE_2D, lastTextureId); 403 404 // The unpack row length only needs to be specified when a new 405 // texture is bound 406 if (hasUnpackRowLength) { 407 glPixelStorei(GL_UNPACK_ROW_LENGTH, width); 408 } 409 } 410 411 // If we can upload a sub-rectangle, use the dirty rect width 412 // instead of the width of the entire texture 413 if (hasUnpackRowLength) { 414 width = dirtyRect->getWidth(); 415 } 416 417#if DEBUG_FONT_RENDERER 418 ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", 419 i, x, y, width, height); 420#endif 421 422 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, 423 GL_ALPHA, GL_UNSIGNED_BYTE, textureData); 424 425 cacheTexture->setDirty(false); 426 } 427 } 428 429 // Reset to default unpack row length to avoid affecting texture 430 // uploads in other parts of the renderer 431 if (hasUnpackRowLength) { 432 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 433 } 434 435 mUploadTexture = false; 436} 437 438void FontRenderer::issueDrawCommand() { 439 bool first = true; 440 bool force = false; 441 442 GLuint lastId = 0; 443 Caches& caches = Caches::getInstance(); 444 445 for (uint32_t i = 0; i < mCacheTextures.size(); i++) { 446 CacheTexture* texture = mCacheTextures[i]; 447 if (texture->canDraw()) { 448 if (first) { 449 if (mFunctor) (*mFunctor)(0, NULL); 450 451 checkTextureUpdate(); 452 caches.bindIndicesBuffer(mIndexBufferID); 453 454 if (!mDrawn) { 455 // If returns true, a VBO was bound and we must 456 // rebind our vertex attrib pointers even if 457 // they have the same values as the current pointers 458 force = caches.unbindMeshBuffer(); 459 } 460 461 caches.activeTexture(0); 462 first = false; 463 } 464 465 glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); 466 texture->setLinearFiltering(mLinearFiltering, false); 467 468 TextureVertex* mesh = texture->mesh(); 469 caches.bindPositionVertexPointer(force, &mesh[0].position[0]); 470 caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]); 471 force = false; 472 473 glDrawElements(GL_TRIANGLES, texture->meshElementCount(), 474 GL_UNSIGNED_SHORT, texture->indices()); 475 476 texture->resetMesh(); 477 } 478 } 479 480 mDrawn = true; 481} 482 483void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, 484 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 485 float x4, float y4, float u4, float v4, CacheTexture* texture) { 486 if (texture != mCurrentCacheTexture) { 487 // Now use the new texture id 488 mCurrentCacheTexture = texture; 489 } 490 491 mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2, 492 x3, y3, u3, v3, x4, y4, u4, v4); 493} 494 495void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, 496 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 497 float x4, float y4, float u4, float v4, CacheTexture* texture) { 498 499 if (mClip && 500 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { 501 return; 502 } 503 504 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 505 506 if (mBounds) { 507 mBounds->left = fmin(mBounds->left, x1); 508 mBounds->top = fmin(mBounds->top, y3); 509 mBounds->right = fmax(mBounds->right, x3); 510 mBounds->bottom = fmax(mBounds->bottom, y1); 511 } 512 513 if (mCurrentCacheTexture->endOfMesh()) { 514 issueDrawCommand(); 515 } 516} 517 518void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, 519 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, 520 float x4, float y4, float u4, float v4, CacheTexture* texture) { 521 522 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); 523 524 if (mBounds) { 525 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4)))); 526 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); 527 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); 528 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); 529 } 530 531 if (mCurrentCacheTexture->endOfMesh()) { 532 issueDrawCommand(); 533 } 534} 535 536void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) { 537 mCurrentFont = Font::create(this, paint, matrix); 538} 539 540FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, 541 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) { 542 checkInit(); 543 544 if (!mCurrentFont) { 545 DropShadow image; 546 image.width = 0; 547 image.height = 0; 548 image.image = NULL; 549 image.penX = 0; 550 image.penY = 0; 551 return image; 552 } 553 554 mDrawn = false; 555 mClip = NULL; 556 mBounds = NULL; 557 558 Rect bounds; 559 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); 560 561 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; 562 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; 563 564#ifdef ANDROID_ENABLE_RENDERSCRIPT 565 // Align buffers for renderscript usage 566 if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) { 567 paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT; 568 } 569 int size = paddedWidth * paddedHeight; 570 uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size); 571#else 572 int size = paddedWidth * paddedHeight; 573 uint8_t* dataBuffer = (uint8_t*) malloc(size); 574#endif 575 576 memset(dataBuffer, 0, size); 577 578 int penX = radius - bounds.left; 579 int penY = radius - bounds.bottom; 580 581 if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) { 582 // text has non-whitespace, so draw and blur to create the shadow 583 // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted 584 // TODO: don't draw pure whitespace in the first place, and avoid needing this check 585 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, 586 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions); 587 588 blurImage(&dataBuffer, paddedWidth, paddedHeight, radius); 589 } 590 591 DropShadow image; 592 image.width = paddedWidth; 593 image.height = paddedHeight; 594 image.image = dataBuffer; 595 image.penX = penX; 596 image.penY = penY; 597 598 return image; 599} 600 601void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) { 602 checkInit(); 603 604 mDrawn = false; 605 mBounds = bounds; 606 mFunctor = functor; 607 mClip = clip; 608} 609 610void FontRenderer::finishRender() { 611 mBounds = NULL; 612 mClip = NULL; 613 614 issueDrawCommand(); 615} 616 617void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) { 618 Font* font = Font::create(this, paint, matrix); 619 font->precache(paint, text, numGlyphs); 620} 621 622bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, 623 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, 624 const float* positions, Rect* bounds, Functor* functor) { 625 if (!mCurrentFont) { 626 ALOGE("No font set"); 627 return false; 628 } 629 630 initRender(clip, bounds, functor); 631 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); 632 finishRender(); 633 634 return mDrawn; 635} 636 637bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, 638 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, 639 float hOffset, float vOffset, Rect* bounds) { 640 if (!mCurrentFont) { 641 ALOGE("No font set"); 642 return false; 643 } 644 645 initRender(clip, bounds, NULL); 646 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); 647 finishRender(); 648 649 return mDrawn; 650} 651 652void FontRenderer::removeFont(const Font* font) { 653 mActiveFonts.remove(font->getDescription()); 654 655 if (mCurrentFont == font) { 656 mCurrentFont = NULL; 657 } 658} 659 660void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) { 661#ifdef ANDROID_ENABLE_RENDERSCRIPT 662 if (width * height * radius >= RS_MIN_INPUT_CUTOFF) { 663 uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); 664 665 if (mRs.get() == 0) { 666 mRs = new RSC::RS(); 667 if (!mRs->init(true, true)) { 668 ALOGE("blur RS failed to init"); 669 } 670 671 mRsElement = RSC::Element::A_8(mRs); 672 mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement); 673 } 674 675 sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0); 676 sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE, 677 RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image); 678 sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE, 679 RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage); 680 681 mRsScript->setRadius(radius); 682 mRsScript->blur(ain, aout); 683 684 // replace the original image's pointer, avoiding a copy back to the original buffer 685 free(*image); 686 *image = outImage; 687 688 return; 689 } 690#endif 691 692 float *gaussian = new float[2 * radius + 1]; 693 Blur::generateGaussianWeights(gaussian, radius); 694 695 uint8_t* scratch = new uint8_t[width * height]; 696 Blur::horizontal(gaussian, radius, *image, scratch, width, height); 697 Blur::vertical(gaussian, radius, scratch, *image, width, height); 698 699 delete[] gaussian; 700 delete[] scratch; 701} 702 703}; // namespace uirenderer 704}; // namespace android 705