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