blob: d1fbfbaa1639edf64e0ff05caf7a68d9cd9f8781 [file] [log] [blame]
Romain Guy694b5192010-07-21 21:33:20 -07001/*
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
Romain Guy694b5192010-07-21 21:33:20 -070019#include <SkUtils.h>
20
Romain Guy51769a62010-07-23 00:28:00 -070021#include <cutils/properties.h>
Romain Guye2d345e2010-09-24 18:39:22 -070022
Romain Guy51769a62010-07-23 00:28:00 -070023#include <utils/Log.h>
24
25#include "FontRenderer.h"
26
Romain Guy694b5192010-07-21 21:33:20 -070027namespace android {
28namespace uirenderer {
29
30///////////////////////////////////////////////////////////////////////////////
Romain Guy51769a62010-07-23 00:28:00 -070031// Defines
32///////////////////////////////////////////////////////////////////////////////
33
34#define DEFAULT_TEXT_CACHE_WIDTH 1024
35#define DEFAULT_TEXT_CACHE_HEIGHT 256
36
37///////////////////////////////////////////////////////////////////////////////
Romain Guy694b5192010-07-21 21:33:20 -070038// Font
39///////////////////////////////////////////////////////////////////////////////
40
Romain Guy325a0f92011-01-05 15:26:55 -080041Font::Font(FontRenderer* state, uint32_t fontId, float fontSize, int flags) :
42 mState(state), mFontId(fontId), mFontSize(fontSize), mFlags(flags) {
Romain Guy694b5192010-07-21 21:33:20 -070043}
44
45
46Font::~Font() {
47 for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
48 if (mState->mActiveFonts[ct] == this) {
49 mState->mActiveFonts.removeAt(ct);
50 break;
51 }
52 }
53
54 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
Romain Guy51769a62010-07-23 00:28:00 -070055 CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i);
Romain Guy694b5192010-07-21 21:33:20 -070056 delete glyph;
57 }
58}
59
60void Font::invalidateTextureCache() {
61 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
62 mCachedGlyphs.valueAt(i)->mIsValid = false;
63 }
64}
65
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -070066void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds) {
67 int nPenX = x + glyph->mBitmapLeft;
68 int nPenY = y + glyph->mBitmapTop;
69
70 int width = (int) glyph->mBitmapWidth;
71 int height = (int) glyph->mBitmapHeight;
72
Romain Guy61c8c9c2010-08-09 20:48:09 -070073 if (bounds->bottom > nPenY) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -070074 bounds->bottom = nPenY;
75 }
Romain Guy61c8c9c2010-08-09 20:48:09 -070076 if (bounds->left > nPenX) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -070077 bounds->left = nPenX;
78 }
Romain Guy61c8c9c2010-08-09 20:48:09 -070079 if (bounds->right < nPenX + width) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -070080 bounds->right = nPenX + width;
81 }
Romain Guy61c8c9c2010-08-09 20:48:09 -070082 if (bounds->top < nPenY + height) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -070083 bounds->top = nPenY + height;
84 }
85}
86
Romain Guy694b5192010-07-21 21:33:20 -070087void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -070088 int nPenX = x + glyph->mBitmapLeft;
89 int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
90
Romain Guy51769a62010-07-23 00:28:00 -070091 float u1 = glyph->mBitmapMinU;
92 float u2 = glyph->mBitmapMaxU;
93 float v1 = glyph->mBitmapMinV;
94 float v2 = glyph->mBitmapMaxV;
95
96 int width = (int) glyph->mBitmapWidth;
97 int height = (int) glyph->mBitmapHeight;
98
99 mState->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
100 nPenX + width, nPenY, 0, u2, v2,
101 nPenX + width, nPenY - height, 0, u2, v1,
102 nPenX, nPenY - height, 0, u1, v1);
Romain Guy694b5192010-07-21 21:33:20 -0700103}
104
Romain Guyb45c0c92010-08-26 20:35:23 -0700105void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
106 uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700107 int nPenX = x + glyph->mBitmapLeft;
108 int nPenY = y + glyph->mBitmapTop;
109
110 uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
111 uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
112
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700113 uint32_t cacheWidth = mState->getCacheWidth();
114 const uint8_t* cacheBuffer = mState->getTextTextureData();
115
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700116 uint32_t cacheX = 0, cacheY = 0;
117 int32_t bX = 0, bY = 0;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700118 for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
119 for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
Romain Guyb45c0c92010-08-26 20:35:23 -0700120 if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700121 LOGE("Skipping invalid index");
122 continue;
123 }
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700124 uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
125 bitmap[bY * bitmapW + bX] = tempCol;
126 }
127 }
128
129}
130
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700131Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) {
Romain Guy1e45aae2010-08-13 19:39:53 -0700132 CachedGlyphInfo* cachedGlyph = NULL;
133 ssize_t index = mCachedGlyphs.indexOfKey(utfChar);
134 if (index >= 0) {
135 cachedGlyph = mCachedGlyphs.valueAt(index);
136 } else {
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700137 cachedGlyph = cacheGlyph(paint, utfChar);
138 }
139
140 // Is the glyph still in texture cache?
141 if (!cachedGlyph->mIsValid) {
142 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
143 updateGlyphCache(paint, skiaGlyph, cachedGlyph);
144 }
145
146 return cachedGlyph;
147}
148
Romain Guy51769a62010-07-23 00:28:00 -0700149void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
Romain Guy61c8c9c2010-08-09 20:48:09 -0700150 int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
151 if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
152 renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
153 bitmapW, bitmapH, NULL);
154 } else {
155 renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 0, 0, NULL);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700156 }
157
158}
159
160void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
Romain Guy61c8c9c2010-08-09 20:48:09 -0700161 int numGlyphs, Rect *bounds) {
162 if (bounds == NULL) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700163 LOGE("No return rectangle provided to measure text");
164 return;
165 }
166 bounds->set(1e6, -1e6, -1e6, 1e6);
167 renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
168}
169
Romain Guy58ef7fb2010-09-13 12:52:37 -0700170#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
Romain Guy2bffd262010-09-12 17:40:02 -0700171
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700172void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
Romain Guy61c8c9c2010-08-09 20:48:09 -0700173 int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
174 uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) {
Romain Guy694b5192010-07-21 21:33:20 -0700175 if (numGlyphs == 0 || text == NULL || len == 0) {
176 return;
177 }
178
Romain Guy2bffd262010-09-12 17:40:02 -0700179 SkFixed penX = SkIntToFixed(x);
180 int penY = y;
Romain Guy694b5192010-07-21 21:33:20 -0700181 int glyphsLeft = 1;
182 if (numGlyphs > 0) {
183 glyphsLeft = numGlyphs;
184 }
185
Romain Guy2bffd262010-09-12 17:40:02 -0700186 SkFixed prevRsbDelta = 0;
187 penX += SK_Fixed1 / 2;
188
Romain Guy694b5192010-07-21 21:33:20 -0700189 text += start;
190
191 while (glyphsLeft > 0) {
Romain Guy694b5192010-07-21 21:33:20 -0700192 int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
193
Romain Guy61c8c9c2010-08-09 20:48:09 -0700194 // Reached the end of the string
Romain Guy694b5192010-07-21 21:33:20 -0700195 if (utfChar < 0) {
196 break;
197 }
198
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700199 CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar);
Romain Guy2bffd262010-09-12 17:40:02 -0700200 penX += SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta);
201 prevRsbDelta = cachedGlyph->mRsbDelta;
Romain Guy694b5192010-07-21 21:33:20 -0700202
203 // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
204 if (cachedGlyph->mIsValid) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700205 switch(mode) {
206 case FRAMEBUFFER:
Romain Guy2bffd262010-09-12 17:40:02 -0700207 drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700208 break;
209 case BITMAP:
Romain Guy2bffd262010-09-12 17:40:02 -0700210 drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bitmap, bitmapW, bitmapH);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700211 break;
212 case MEASURE:
Romain Guy2bffd262010-09-12 17:40:02 -0700213 measureCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bounds);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700214 break;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700215 }
Romain Guy694b5192010-07-21 21:33:20 -0700216 }
217
Romain Guy2bffd262010-09-12 17:40:02 -0700218 penX += cachedGlyph->mAdvanceX;
Romain Guy694b5192010-07-21 21:33:20 -0700219
220 // If we were given a specific number of glyphs, decrement
221 if (numGlyphs > 0) {
222 glyphsLeft--;
223 }
224 }
225}
226
Romain Guy51769a62010-07-23 00:28:00 -0700227void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
Romain Guy694b5192010-07-21 21:33:20 -0700228 glyph->mAdvanceX = skiaGlyph.fAdvanceX;
229 glyph->mAdvanceY = skiaGlyph.fAdvanceY;
230 glyph->mBitmapLeft = skiaGlyph.fLeft;
231 glyph->mBitmapTop = skiaGlyph.fTop;
Romain Guy2bffd262010-09-12 17:40:02 -0700232 glyph->mLsbDelta = skiaGlyph.fLsbDelta;
233 glyph->mRsbDelta = skiaGlyph.fRsbDelta;
Romain Guy694b5192010-07-21 21:33:20 -0700234
235 uint32_t startX = 0;
236 uint32_t startY = 0;
237
Romain Guy694b5192010-07-21 21:33:20 -0700238 // Get the bitmap for the glyph
239 paint->findImage(skiaGlyph);
Romain Guy51769a62010-07-23 00:28:00 -0700240 glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY);
Romain Guy694b5192010-07-21 21:33:20 -0700241
242 if (!glyph->mIsValid) {
243 return;
244 }
245
246 uint32_t endX = startX + skiaGlyph.fWidth;
247 uint32_t endY = startY + skiaGlyph.fHeight;
248
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700249 glyph->mStartX = startX;
250 glyph->mStartY = startY;
Romain Guy694b5192010-07-21 21:33:20 -0700251 glyph->mBitmapWidth = skiaGlyph.fWidth;
252 glyph->mBitmapHeight = skiaGlyph.fHeight;
253
Romain Guy51769a62010-07-23 00:28:00 -0700254 uint32_t cacheWidth = mState->getCacheWidth();
255 uint32_t cacheHeight = mState->getCacheHeight();
Romain Guy694b5192010-07-21 21:33:20 -0700256
257 glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
258 glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
259 glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
260 glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
261
Romain Guy51769a62010-07-23 00:28:00 -0700262 mState->mUploadTexture = true;
Romain Guy694b5192010-07-21 21:33:20 -0700263}
264
Romain Guy51769a62010-07-23 00:28:00 -0700265Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
266 CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
Romain Guy694b5192010-07-21 21:33:20 -0700267 mCachedGlyphs.add(glyph, newGlyph);
268
269 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
270 newGlyph->mGlyphIndex = skiaGlyph.fID;
271 newGlyph->mIsValid = false;
272
273 updateGlyphCache(paint, skiaGlyph, newGlyph);
274
275 return newGlyph;
276}
277
Romain Guy325a0f92011-01-05 15:26:55 -0800278Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize, int flags) {
Romain Guy694b5192010-07-21 21:33:20 -0700279 Vector<Font*> &activeFonts = state->mActiveFonts;
280
281 for (uint32_t i = 0; i < activeFonts.size(); i++) {
Romain Guy51769a62010-07-23 00:28:00 -0700282 Font* font = activeFonts[i];
Romain Guy325a0f92011-01-05 15:26:55 -0800283 if (font->mFontId == fontId && font->mFontSize == fontSize && font->mFlags == flags) {
Romain Guy51769a62010-07-23 00:28:00 -0700284 return font;
Romain Guy694b5192010-07-21 21:33:20 -0700285 }
286 }
287
Romain Guy325a0f92011-01-05 15:26:55 -0800288 Font* newFont = new Font(state, fontId, fontSize, flags);
Romain Guy694b5192010-07-21 21:33:20 -0700289 activeFonts.push(newFont);
290 return newFont;
291}
292
293///////////////////////////////////////////////////////////////////////////////
294// FontRenderer
295///////////////////////////////////////////////////////////////////////////////
296
297FontRenderer::FontRenderer() {
Romain Guy51769a62010-07-23 00:28:00 -0700298 LOGD("Creating FontRenderer");
299
Romain Guyb45c0c92010-08-26 20:35:23 -0700300 mGammaTable = NULL;
Romain Guy694b5192010-07-21 21:33:20 -0700301 mInitialized = false;
302 mMaxNumberOfQuads = 1024;
303 mCurrentQuadIndex = 0;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700304 mTextureId = 0;
Romain Guy694b5192010-07-21 21:33:20 -0700305
Romain Guy9cccc2b92010-08-07 23:46:15 -0700306 mTextMeshPtr = NULL;
307 mTextTexture = NULL;
308
Romain Guy694b5192010-07-21 21:33:20 -0700309 mIndexBufferID = 0;
310
Romain Guy51769a62010-07-23 00:28:00 -0700311 mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700312 mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
Romain Guy51769a62010-07-23 00:28:00 -0700313
314 char property[PROPERTY_VALUE_MAX];
315 if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
316 LOGD(" Setting text cache width to %s pixels", property);
317 mCacheWidth = atoi(property);
318 } else {
319 LOGD(" Using default text cache width of %i pixels", mCacheWidth);
320 }
321
322 if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) {
323 LOGD(" Setting text cache width to %s pixels", property);
324 mCacheHeight = atoi(property);
325 } else {
326 LOGD(" Using default text cache height of %i pixels", mCacheHeight);
327 }
Romain Guy694b5192010-07-21 21:33:20 -0700328}
329
330FontRenderer::~FontRenderer() {
331 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
332 delete mCacheLines[i];
333 }
334 mCacheLines.clear();
335
Romain Guy9cccc2b92010-08-07 23:46:15 -0700336 if (mInitialized) {
337 delete[] mTextMeshPtr;
338 delete[] mTextTexture;
339 }
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700340
Romain Guy9cccc2b92010-08-07 23:46:15 -0700341 if (mTextureId) {
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700342 glDeleteTextures(1, &mTextureId);
343 }
Romain Guy694b5192010-07-21 21:33:20 -0700344
345 Vector<Font*> fontsToDereference = mActiveFonts;
346 for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
347 delete fontsToDereference[i];
348 }
349}
350
351void FontRenderer::flushAllAndInvalidate() {
352 if (mCurrentQuadIndex != 0) {
353 issueDrawCommand();
354 mCurrentQuadIndex = 0;
355 }
356 for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
357 mActiveFonts[i]->invalidateTextureCache();
358 }
359 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
360 mCacheLines[i]->mCurrentCol = 0;
361 }
362}
363
Romain Guy51769a62010-07-23 00:28:00 -0700364bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
Romain Guy694b5192010-07-21 21:33:20 -0700365 // If the glyph is too tall, don't cache it
Romain Guy2bffd262010-09-12 17:40:02 -0700366 if (glyph.fHeight > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
Romain Guy694b5192010-07-21 21:33:20 -0700367 LOGE("Font size to large to fit in cache. width, height = %i, %i",
368 (int) glyph.fWidth, (int) glyph.fHeight);
369 return false;
370 }
371
372 // Now copy the bitmap into the cache texture
373 uint32_t startX = 0;
374 uint32_t startY = 0;
375
376 bool bitmapFit = false;
377 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
378 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
379 if (bitmapFit) {
380 break;
381 }
382 }
383
384 // If the new glyph didn't fit, flush the state so far and invalidate everything
385 if (!bitmapFit) {
386 flushAllAndInvalidate();
387
388 // Try to fit it again
389 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
390 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
391 if (bitmapFit) {
392 break;
393 }
394 }
395
396 // if we still don't fit, something is wrong and we shouldn't draw
397 if (!bitmapFit) {
398 LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
399 (int) glyph.fWidth, (int) glyph.fHeight);
400 return false;
401 }
402 }
403
404 *retOriginX = startX;
405 *retOriginY = startY;
406
407 uint32_t endX = startX + glyph.fWidth;
408 uint32_t endY = startY + glyph.fHeight;
409
410 uint32_t cacheWidth = mCacheWidth;
411
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700412 uint8_t* cacheBuffer = mTextTexture;
413 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
Romain Guy694b5192010-07-21 21:33:20 -0700414 unsigned int stride = glyph.rowBytes();
415
416 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
417 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
418 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700419 uint8_t tempCol = bitmapBuffer[bY * stride + bX];
Romain Guyb45c0c92010-08-26 20:35:23 -0700420 cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
Romain Guy694b5192010-07-21 21:33:20 -0700421 }
422 }
423
424 return true;
425}
426
427void FontRenderer::initTextTexture() {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700428 mTextTexture = new uint8_t[mCacheWidth * mCacheHeight];
Romain Guy1de10832010-10-03 14:37:09 -0700429 memset(mTextTexture, 0, mCacheWidth * mCacheHeight * sizeof(uint8_t));
430
Romain Guy694b5192010-07-21 21:33:20 -0700431 mUploadTexture = false;
432
433 glGenTextures(1, &mTextureId);
434 glBindTexture(GL_TEXTURE_2D, mTextureId);
435 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700436 // Initialize texture dimentions
437 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
Romain Guy61c8c9c2010-08-09 20:48:09 -0700438 GL_ALPHA, GL_UNSIGNED_BYTE, 0);
Romain Guy694b5192010-07-21 21:33:20 -0700439
Romain Guye8cb9c142010-10-04 14:14:11 -0700440 mLinearFiltering = false;
441 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
442 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
Romain Guy694b5192010-07-21 21:33:20 -0700443
444 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
445 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
446
447 // Split up our cache texture into lines of certain widths
448 int nextLine = 0;
Romain Guy7975fb62010-10-01 16:36:14 -0700449 mCacheLines.push(new CacheTextureLine(mCacheWidth, 18, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700450 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy7975fb62010-10-01 16:36:14 -0700451 mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700452 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy7975fb62010-10-01 16:36:14 -0700453 mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700454 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy7975fb62010-10-01 16:36:14 -0700455 mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700456 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy7975fb62010-10-01 16:36:14 -0700457 mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700458 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy7975fb62010-10-01 16:36:14 -0700459 mCacheLines.push(new CacheTextureLine(mCacheWidth, 42, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700460 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700461 mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700462}
463
464// Avoid having to reallocate memory and render quad by quad
465void FontRenderer::initVertexArrayBuffers() {
466 uint32_t numIndicies = mMaxNumberOfQuads * 6;
467 uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
Romain Guy51769a62010-07-23 00:28:00 -0700468 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
Romain Guy694b5192010-07-21 21:33:20 -0700469
470 // Four verts, two triangles , six indices per quad
471 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
472 int i6 = i * 6;
473 int i4 = i * 4;
474
475 indexBufferData[i6 + 0] = i4 + 0;
476 indexBufferData[i6 + 1] = i4 + 1;
477 indexBufferData[i6 + 2] = i4 + 2;
478
479 indexBufferData[i6 + 3] = i4 + 0;
480 indexBufferData[i6 + 4] = i4 + 2;
481 indexBufferData[i6 + 5] = i4 + 3;
482 }
483
484 glGenBuffers(1, &mIndexBufferID);
Romain Guy5d794412010-10-13 16:36:22 -0700485 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
486 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
487 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
Romain Guy694b5192010-07-21 21:33:20 -0700488
489 free(indexBufferData);
490
491 uint32_t coordSize = 3;
492 uint32_t uvSize = 2;
493 uint32_t vertsPerQuad = 4;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700494 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
495 mTextMeshPtr = new float[vertexBufferSize];
Romain Guy694b5192010-07-21 21:33:20 -0700496}
497
498// We don't want to allocate anything unless we actually draw text
499void FontRenderer::checkInit() {
500 if (mInitialized) {
501 return;
502 }
503
504 initTextTexture();
505 initVertexArrayBuffers();
506
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700507 // We store a string with letters in a rough frequency of occurrence
508 mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq ");
509 mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ");
510 mLatinPrecache += String16(",.?!()-+@;:`'");
511 mLatinPrecache += String16("0123456789");
512
Romain Guy694b5192010-07-21 21:33:20 -0700513 mInitialized = true;
514}
515
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700516void FontRenderer::checkTextureUpdate() {
517 if (!mUploadTexture) {
518 return;
Romain Guy694b5192010-07-21 21:33:20 -0700519 }
520
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700521 glBindTexture(GL_TEXTURE_2D, mTextureId);
522
523 // Iterate over all the cache lines and see which ones need to be updated
524 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
525 CacheTextureLine* cl = mCacheLines[i];
526 if(cl->mDirty) {
527 uint32_t xOffset = 0;
528 uint32_t yOffset = cl->mCurrentRow;
529 uint32_t width = mCacheWidth;
530 uint32_t height = cl->mMaxHeight;
Romain Guy1e45aae2010-08-13 19:39:53 -0700531 void* textureData = mTextTexture + yOffset*width;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700532
533 glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
Romain Guy1e45aae2010-08-13 19:39:53 -0700534 GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700535
536 cl->mDirty = false;
537 }
538 }
539
540 mUploadTexture = false;
541}
542
543void FontRenderer::issueDrawCommand() {
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700544 checkTextureUpdate();
545
Romain Guy51769a62010-07-23 00:28:00 -0700546 float* vtx = mTextMeshPtr;
547 float* tex = vtx + 3;
Romain Guy694b5192010-07-21 21:33:20 -0700548
549 // position is slot 0
550 uint32_t slot = 0;
551 glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
552
553 // texture0 is slot 1
554 slot = 1;
555 glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
556
557 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
558 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
Romain Guy5b3b3522010-10-27 18:57:51 -0700559
560 mDrawn = true;
Romain Guy694b5192010-07-21 21:33:20 -0700561}
562
563void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
564 float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
565 float x4, float y4, float z4, float u4, float v4) {
Romain Guy09147fb2010-07-22 13:08:20 -0700566 if (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom) {
567 return;
568 }
569
Romain Guy694b5192010-07-21 21:33:20 -0700570 const uint32_t vertsPerQuad = 4;
571 const uint32_t floatsPerVert = 5;
Romain Guy51769a62010-07-23 00:28:00 -0700572 float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
Romain Guy694b5192010-07-21 21:33:20 -0700573
Romain Guy694b5192010-07-21 21:33:20 -0700574 (*currentPos++) = x1;
575 (*currentPos++) = y1;
576 (*currentPos++) = z1;
577 (*currentPos++) = u1;
578 (*currentPos++) = v1;
579
580 (*currentPos++) = x2;
581 (*currentPos++) = y2;
582 (*currentPos++) = z2;
583 (*currentPos++) = u2;
584 (*currentPos++) = v2;
585
586 (*currentPos++) = x3;
587 (*currentPos++) = y3;
588 (*currentPos++) = z3;
589 (*currentPos++) = u3;
590 (*currentPos++) = v3;
591
592 (*currentPos++) = x4;
593 (*currentPos++) = y4;
594 (*currentPos++) = z4;
595 (*currentPos++) = u4;
596 (*currentPos++) = v4;
597
598 mCurrentQuadIndex++;
599
Romain Guy5b3b3522010-10-27 18:57:51 -0700600 if (mBounds) {
601 mBounds->left = fmin(mBounds->left, x1);
602 mBounds->top = fmin(mBounds->top, y3);
603 mBounds->right = fmax(mBounds->right, x3);
604 mBounds->bottom = fmax(mBounds->bottom, y1);
605 }
606
Romain Guy694b5192010-07-21 21:33:20 -0700607 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
608 issueDrawCommand();
609 mCurrentQuadIndex = 0;
610 }
611}
612
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700613uint32_t FontRenderer::getRemainingCacheCapacity() {
614 uint32_t remainingCapacity = 0;
615 float totalPixels = 0;
616 for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
617 remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol);
618 totalPixels += mCacheLines[i]->mMaxWidth;
619 }
620 remainingCapacity = (remainingCapacity * 100) / totalPixels;
621 return remainingCapacity;
622}
623
624void FontRenderer::precacheLatin(SkPaint* paint) {
625 // Remaining capacity is measured in %
626 uint32_t remainingCapacity = getRemainingCacheCapacity();
627 uint32_t precacheIdx = 0;
Romain Guy054dc182010-10-15 17:55:25 -0700628 while (remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) {
629 mCurrentFont->getCachedUTFChar(paint, (int32_t) mLatinPrecache[precacheIdx]);
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700630 remainingCapacity = getRemainingCacheCapacity();
631 precacheIdx ++;
632 }
633}
634
635void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
636 uint32_t currentNumFonts = mActiveFonts.size();
Romain Guy325a0f92011-01-05 15:26:55 -0800637 int flags = 0;
638 if (paint->isFakeBoldText()) {
639 flags |= Font::kFakeBold;
640 }
641 mCurrentFont = Font::create(this, fontId, fontSize, flags);
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700642
643 const float maxPrecacheFontSize = 40.0f;
644 bool isNewFont = currentNumFonts != mActiveFonts.size();
645
Romain Guy2bffd262010-09-12 17:40:02 -0700646 if (isNewFont && fontSize <= maxPrecacheFontSize) {
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700647 precacheLatin(paint);
648 }
Romain Guy694b5192010-07-21 21:33:20 -0700649}
Romain Guy7975fb62010-10-01 16:36:14 -0700650
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700651FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
Romain Guy1e45aae2010-08-13 19:39:53 -0700652 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
653 checkInit();
654
655 if (!mCurrentFont) {
656 DropShadow image;
657 image.width = 0;
658 image.height = 0;
659 image.image = NULL;
660 image.penX = 0;
661 image.penY = 0;
662 return image;
663 }
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700664
665 Rect bounds;
666 mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
Romain Guy1e45aae2010-08-13 19:39:53 -0700667 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
668 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700669 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
Romain Guy1e45aae2010-08-13 19:39:53 -0700670 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700671 dataBuffer[i] = 0;
672 }
Romain Guy1e45aae2010-08-13 19:39:53 -0700673
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700674 int penX = radius - bounds.left;
675 int penY = radius - bounds.bottom;
676
677 mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
Romain Guy1e45aae2010-08-13 19:39:53 -0700678 dataBuffer, paddedWidth, paddedHeight);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700679 blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
680
681 DropShadow image;
682 image.width = paddedWidth;
683 image.height = paddedHeight;
684 image.image = dataBuffer;
685 image.penX = penX;
686 image.penY = penY;
687 return image;
688}
Romain Guy694b5192010-07-21 21:33:20 -0700689
Romain Guy5b3b3522010-10-27 18:57:51 -0700690bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
691 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) {
Romain Guy694b5192010-07-21 21:33:20 -0700692 checkInit();
693
Romain Guy09147fb2010-07-22 13:08:20 -0700694 if (!mCurrentFont) {
695 LOGE("No font set");
Romain Guy5b3b3522010-10-27 18:57:51 -0700696 return false;
Romain Guy694b5192010-07-21 21:33:20 -0700697 }
698
Romain Guy5b3b3522010-10-27 18:57:51 -0700699 mDrawn = false;
700 mBounds = bounds;
Romain Guy09147fb2010-07-22 13:08:20 -0700701 mClip = clip;
Romain Guy51769a62010-07-23 00:28:00 -0700702 mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y);
Romain Guy5b3b3522010-10-27 18:57:51 -0700703 mBounds = NULL;
Romain Guy694b5192010-07-21 21:33:20 -0700704
705 if (mCurrentQuadIndex != 0) {
706 issueDrawCommand();
707 mCurrentQuadIndex = 0;
708 }
Romain Guy5b3b3522010-10-27 18:57:51 -0700709
710 return mDrawn;
Romain Guy694b5192010-07-21 21:33:20 -0700711}
712
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700713void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
714 // Compute gaussian weights for the blur
715 // e is the euler's number
716 float e = 2.718281828459045f;
717 float pi = 3.1415926535897932f;
718 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
719 // x is of the form [-radius .. 0 .. radius]
720 // and sigma varies with radius.
721 // Based on some experimental radius values and sigma's
722 // we approximately fit sigma = f(radius) as
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700723 // sigma = radius * 0.3 + 0.6
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700724 // The larger the radius gets, the more our gaussian blur
725 // will resemble a box blur since with large sigma
726 // the gaussian curve begins to lose its shape
Romain Guy325a0f92011-01-05 15:26:55 -0800727 float sigma = 0.3f * (float) radius + 0.6f;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700728
729 // Now compute the coefficints
730 // We will store some redundant values to save some math during
731 // the blur calculations
732 // precompute some values
733 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
734 float coeff2 = - 1.0f / (2.0f * sigma * sigma);
735
736 float normalizeFactor = 0.0f;
Romain Guy325a0f92011-01-05 15:26:55 -0800737 for (int32_t r = -radius; r <= radius; r ++) {
Romain Guy7975fb62010-10-01 16:36:14 -0700738 float floatR = (float) r;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700739 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
740 normalizeFactor += weights[r + radius];
741 }
742
743 //Now we need to normalize the weights because all our coefficients need to add up to one
744 normalizeFactor = 1.0f / normalizeFactor;
Romain Guy325a0f92011-01-05 15:26:55 -0800745 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700746 weights[r + radius] *= normalizeFactor;
747 }
748}
749
750void FontRenderer::horizontalBlur(float* weights, int32_t radius,
Romain Guy1e45aae2010-08-13 19:39:53 -0700751 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700752 float blurredPixel = 0.0f;
753 float currentPixel = 0.0f;
754
Romain Guy325a0f92011-01-05 15:26:55 -0800755 for (int32_t y = 0; y < height; y ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700756
757 const uint8_t* input = source + y * width;
758 uint8_t* output = dest + y * width;
759
Romain Guy325a0f92011-01-05 15:26:55 -0800760 for (int32_t x = 0; x < width; x ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700761 blurredPixel = 0.0f;
762 const float* gPtr = weights;
763 // Optimization for non-border pixels
Romain Guy325a0f92011-01-05 15:26:55 -0800764 if (x > radius && x < (width - radius)) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700765 const uint8_t *i = input + (x - radius);
Romain Guy325a0f92011-01-05 15:26:55 -0800766 for (int r = -radius; r <= radius; r ++) {
Romain Guy7975fb62010-10-01 16:36:14 -0700767 currentPixel = (float) (*i);
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700768 blurredPixel += currentPixel * gPtr[0];
769 gPtr++;
770 i++;
771 }
772 } else {
Romain Guy325a0f92011-01-05 15:26:55 -0800773 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700774 // Stepping left and right away from the pixel
775 int validW = x + r;
Romain Guy325a0f92011-01-05 15:26:55 -0800776 if (validW < 0) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700777 validW = 0;
778 }
Romain Guy325a0f92011-01-05 15:26:55 -0800779 if (validW > width - 1) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700780 validW = width - 1;
781 }
782
Romain Guy325a0f92011-01-05 15:26:55 -0800783 currentPixel = (float) input[validW];
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700784 blurredPixel += currentPixel * gPtr[0];
785 gPtr++;
786 }
787 }
788 *output = (uint8_t)blurredPixel;
789 output ++;
790 }
791 }
792}
793
794void FontRenderer::verticalBlur(float* weights, int32_t radius,
Romain Guy1e45aae2010-08-13 19:39:53 -0700795 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700796 float blurredPixel = 0.0f;
797 float currentPixel = 0.0f;
798
Romain Guy325a0f92011-01-05 15:26:55 -0800799 for (int32_t y = 0; y < height; y ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700800
801 uint8_t* output = dest + y * width;
802
Romain Guy325a0f92011-01-05 15:26:55 -0800803 for (int32_t x = 0; x < width; x ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700804 blurredPixel = 0.0f;
805 const float* gPtr = weights;
806 const uint8_t* input = source + x;
807 // Optimization for non-border pixels
Romain Guy325a0f92011-01-05 15:26:55 -0800808 if (y > radius && y < (height - radius)) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700809 const uint8_t *i = input + ((y - radius) * width);
Romain Guy325a0f92011-01-05 15:26:55 -0800810 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700811 currentPixel = (float)(*i);
812 blurredPixel += currentPixel * gPtr[0];
813 gPtr++;
814 i += width;
815 }
816 } else {
Romain Guy325a0f92011-01-05 15:26:55 -0800817 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700818 int validH = y + r;
819 // Clamp to zero and width
Romain Guy325a0f92011-01-05 15:26:55 -0800820 if (validH < 0) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700821 validH = 0;
822 }
Romain Guy325a0f92011-01-05 15:26:55 -0800823 if (validH > height - 1) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700824 validH = height - 1;
825 }
826
827 const uint8_t *i = input + validH * width;
Romain Guy325a0f92011-01-05 15:26:55 -0800828 currentPixel = (float) (*i);
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700829 blurredPixel += currentPixel * gPtr[0];
830 gPtr++;
831 }
832 }
Romain Guy325a0f92011-01-05 15:26:55 -0800833 *output = (uint8_t) blurredPixel;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700834 output ++;
835 }
836 }
837}
838
839
840void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
841 float *gaussian = new float[2 * radius + 1];
842 computeGaussianWeights(gaussian, radius);
843 uint8_t* scratch = new uint8_t[width * height];
844 horizontalBlur(gaussian, radius, image, scratch, width, height);
845 verticalBlur(gaussian, radius, scratch, image, width, height);
846 delete[] gaussian;
847 delete[] scratch;
848}
849
Romain Guy694b5192010-07-21 21:33:20 -0700850}; // namespace uirenderer
851}; // namespace android