Home | History | Annotate | Download | only in screenrecord
      1 /*
      2  * Copyright 2013 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 "ScreenRecord"
     18 //#define LOG_NDEBUG 0
     19 #include <utils/Log.h>
     20 
     21 #include "TextRenderer.h"
     22 
     23 #include <assert.h>
     24 
     25 namespace android {
     26 #include "FontBitmap.h"
     27 };
     28 
     29 using namespace android;
     30 
     31 const char TextRenderer::kWhitespace[] = " \t\n\r";
     32 
     33 bool TextRenderer::mInitialized = false;
     34 uint32_t TextRenderer::mXOffset[FontBitmap::numGlyphs];
     35 
     36 void TextRenderer::initOnce() {
     37     if (!mInitialized) {
     38         initXOffset();
     39         mInitialized = true;
     40     }
     41 }
     42 
     43 void TextRenderer::initXOffset() {
     44     // Generate a table of X offsets.  They start at zero and reset whenever
     45     // we move down a line (i.e. the Y offset changes).  The offset increases
     46     // by one pixel more than the width because the generator left a gap to
     47     // avoid reading pixels from adjacent glyphs in the texture filter.
     48     uint16_t offset = 0;
     49     uint16_t prevYOffset = (int16_t) -1;
     50     for (unsigned int i = 0; i < FontBitmap::numGlyphs; i++) {
     51         if (prevYOffset != FontBitmap::yoffset[i]) {
     52             prevYOffset = FontBitmap::yoffset[i];
     53             offset = 0;
     54         }
     55         mXOffset[i] = offset;
     56         offset += FontBitmap::glyphWidth[i] + 1;
     57     }
     58 }
     59 
     60 static bool isPowerOfTwo(uint32_t val) {
     61     // a/k/a "is exactly one bit set"; note returns true for 0
     62     return (val & (val -1)) == 0;
     63 }
     64 
     65 static uint32_t powerOfTwoCeil(uint32_t val) {
     66     // drop it, smear the bits across, pop it
     67     val--;
     68     val |= val >> 1;
     69     val |= val >> 2;
     70     val |= val >> 4;
     71     val |= val >> 8;
     72     val |= val >> 16;
     73     val++;
     74 
     75     return val;
     76 }
     77 
     78 float TextRenderer::getGlyphHeight() const {
     79     return FontBitmap::maxGlyphHeight;
     80 }
     81 
     82 status_t TextRenderer::loadIntoTexture() {
     83     ALOGV("Font::loadIntoTexture");
     84 
     85     glGenTextures(1, &mTextureName);
     86     if (mTextureName == 0) {
     87         ALOGE("glGenTextures failed: %#x", glGetError());
     88         return UNKNOWN_ERROR;
     89     }
     90     glBindTexture(GL_TEXTURE_2D, mTextureName);
     91     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     92     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     93 
     94     // The pixel data is stored as combined color+alpha, 8 bits per pixel.
     95     // It's guaranteed to be a power-of-two wide, but we cut off the height
     96     // where the data ends.  We want to expand it to a power-of-two bitmap
     97     // with ARGB data and hand that to glTexImage2D.
     98 
     99     if (!isPowerOfTwo(FontBitmap::width)) {
    100         ALOGE("npot glyph bitmap width %u", FontBitmap::width);
    101         return UNKNOWN_ERROR;
    102     }
    103 
    104     uint32_t potHeight = powerOfTwoCeil(FontBitmap::height);
    105     uint8_t* rgbaPixels = new uint8_t[FontBitmap::width * potHeight * 4];
    106     memset(rgbaPixels, 0, FontBitmap::width * potHeight * 4);
    107     uint8_t* pix = rgbaPixels;
    108 
    109     for (unsigned int i = 0; i < FontBitmap::width * FontBitmap::height; i++) {
    110         uint8_t alpha, color;
    111         if ((FontBitmap::pixels[i] & 1) == 0) {
    112             // black pixel with varying alpha
    113             color = 0x00;
    114             alpha = FontBitmap::pixels[i] & ~1;
    115         } else {
    116             // opaque grey pixel
    117             color = FontBitmap::pixels[i] & ~1;
    118             alpha = 0xff;
    119         }
    120         *pix++ = color;
    121         *pix++ = color;
    122         *pix++ = color;
    123         *pix++ = alpha;
    124     }
    125 
    126     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FontBitmap::width, potHeight, 0,
    127             GL_RGBA, GL_UNSIGNED_BYTE, rgbaPixels);
    128     delete[] rgbaPixels;
    129     GLint glErr = glGetError();
    130     if (glErr != 0) {
    131         ALOGE("glTexImage2D failed: %#x", glErr);
    132         return UNKNOWN_ERROR;
    133     }
    134     return NO_ERROR;
    135 }
    136 
    137 void TextRenderer::setProportionalScale(float linesPerScreen) {
    138     if (mScreenWidth == 0 || mScreenHeight == 0) {
    139         ALOGW("setFontScale: can't set scale for width=%d height=%d",
    140                 mScreenWidth, mScreenHeight);
    141         return;
    142     }
    143     float tallest = mScreenWidth > mScreenHeight ? mScreenWidth : mScreenHeight;
    144     setScale(tallest / (linesPerScreen * getGlyphHeight()));
    145 }
    146 
    147 float TextRenderer::computeScaledStringWidth(const String8& str8) const {
    148     // String8.length() isn't documented, but I'm assuming it will return
    149     // the number of characters rather than the number of bytes.  Since
    150     // we can only display ASCII we want to ignore anything else, so we
    151     // just convert to char* -- but String8 doesn't document what it does
    152     // with values outside 0-255.  So just convert to char* and use strlen()
    153     // to see what we get.
    154     const char* str = str8.string();
    155     return computeScaledStringWidth(str, strlen(str));
    156 }
    157 
    158 size_t TextRenderer::glyphIndex(char ch) const {
    159     size_t chi = ch - FontBitmap::firstGlyphChar;
    160     if (chi >= FontBitmap::numGlyphs) {
    161         chi = '?' - FontBitmap::firstGlyphChar;
    162     }
    163     assert(chi < FontBitmap::numGlyphs);
    164     return chi;
    165 }
    166 
    167 float TextRenderer::computeScaledStringWidth(const char* str,
    168         size_t len) const {
    169     float width = 0.0f;
    170     for (size_t i = 0; i < len; i++) {
    171         size_t chi = glyphIndex(str[i]);
    172         float glyphWidth = FontBitmap::glyphWidth[chi];
    173         width += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale;
    174     }
    175 
    176     return width;
    177 }
    178 
    179 void TextRenderer::drawString(const Program& program, const float* texMatrix,
    180         float x, float y, const String8& str8) const {
    181     ALOGV("drawString %.3f,%.3f '%s' (scale=%.3f)", x, y, str8.string(),mScale);
    182     initOnce();
    183 
    184     // We want to draw the entire string with a single GLES call.  We
    185     // generate two arrays, one with screen coordinates, one with texture
    186     // coordinates.  Need two triangles per character.
    187     const char* str = str8.string();
    188     size_t len = strlen(str);       // again, unsure about String8 handling
    189 
    190     const size_t quadCoords =
    191             2 /*triangles*/ * 3 /*vertex/tri*/ * 2 /*coord/vertex*/;
    192     float vertices[len * quadCoords];
    193     float texes[len * quadCoords];
    194 
    195     float fullTexWidth = FontBitmap::width;
    196     float fullTexHeight = powerOfTwoCeil(FontBitmap::height);
    197     for (size_t i = 0; i < len; i++) {
    198         size_t chi = glyphIndex(str[i]);
    199         float glyphWidth = FontBitmap::glyphWidth[chi];
    200         float glyphHeight = FontBitmap::maxGlyphHeight;
    201 
    202         float vertLeft = x;
    203         float vertRight = x + glyphWidth * mScale;
    204         float vertTop = y;
    205         float vertBottom = y + glyphHeight * mScale;
    206 
    207         // Lowest-numbered glyph is in top-left of bitmap, which puts it at
    208         // the bottom-left in texture coordinates.
    209         float texLeft = mXOffset[chi] / fullTexWidth;
    210         float texRight = (mXOffset[chi] + glyphWidth) / fullTexWidth;
    211         float texTop = FontBitmap::yoffset[chi] / fullTexHeight;
    212         float texBottom = (FontBitmap::yoffset[chi] + glyphHeight) /
    213                 fullTexHeight;
    214 
    215         size_t off = i * quadCoords;
    216         vertices[off +  0] = vertLeft;
    217         vertices[off +  1] = vertBottom;
    218         vertices[off +  2] = vertRight;
    219         vertices[off +  3] = vertBottom;
    220         vertices[off +  4] = vertLeft;
    221         vertices[off +  5] = vertTop;
    222         vertices[off +  6] = vertLeft;
    223         vertices[off +  7] = vertTop;
    224         vertices[off +  8] = vertRight;
    225         vertices[off +  9] = vertBottom;
    226         vertices[off + 10] = vertRight;
    227         vertices[off + 11] = vertTop;
    228         texes[off +  0] = texLeft;
    229         texes[off +  1] = texBottom;
    230         texes[off +  2] = texRight;
    231         texes[off +  3] = texBottom;
    232         texes[off +  4] = texLeft;
    233         texes[off +  5] = texTop;
    234         texes[off +  6] = texLeft;
    235         texes[off +  7] = texTop;
    236         texes[off +  8] = texRight;
    237         texes[off +  9] = texBottom;
    238         texes[off + 10] = texRight;
    239         texes[off + 11] = texTop;
    240 
    241         // We added 1-pixel padding in the texture, so we want to advance by
    242         // one less.  Also, each glyph is surrounded by a black outline, which
    243         // we want to merge.
    244         x += (glyphWidth - 1 - FontBitmap::outlineWidth) * mScale;
    245     }
    246 
    247     program.drawTriangles(mTextureName, texMatrix, vertices, texes,
    248             len * quadCoords / 2);
    249 }
    250 
    251 float TextRenderer::drawWrappedString(const Program& texRender,
    252         float xpos, float ypos, const String8& str) {
    253     ALOGV("drawWrappedString %.3f,%.3f '%s'", xpos, ypos, str.string());
    254     initOnce();
    255 
    256     if (mScreenWidth == 0 || mScreenHeight == 0) {
    257         ALOGW("drawWrappedString: can't wrap with width=%d height=%d",
    258                 mScreenWidth, mScreenHeight);
    259         return ypos;
    260     }
    261 
    262     const float indentWidth = mIndentMult * getScale();
    263     if (xpos < mBorderWidth) {
    264         xpos = mBorderWidth;
    265     }
    266     if (ypos < mBorderWidth) {
    267         ypos = mBorderWidth;
    268     }
    269 
    270     const size_t maxWidth = (mScreenWidth - mBorderWidth) - xpos;
    271     if (maxWidth < 1) {
    272         ALOGE("Unable to render text: xpos=%.3f border=%.3f width=%u",
    273                 xpos, mBorderWidth, mScreenWidth);
    274         return ypos;
    275     }
    276     float stringWidth = computeScaledStringWidth(str);
    277     if (stringWidth <= maxWidth) {
    278         // Trivial case.
    279         drawString(texRender, Program::kIdentity, xpos, ypos, str);
    280         ypos += getScaledGlyphHeight();
    281     } else {
    282         // We need to break the string into pieces, ideally at whitespace
    283         // boundaries.
    284         char* mangle = strdup(str.string());
    285         char* start = mangle;
    286         while (start != NULL) {
    287             float xposAdj = (start == mangle) ? xpos : xpos + indentWidth;
    288             char* brk = breakString(start,
    289                     (float) (mScreenWidth - mBorderWidth - xposAdj));
    290             if (brk == NULL) {
    291                 // draw full string
    292                 drawString(texRender, Program::kIdentity, xposAdj, ypos,
    293                         String8(start));
    294                 start = NULL;
    295             } else {
    296                 // draw partial string
    297                 char ch = *brk;
    298                 *brk = '\0';
    299                 drawString(texRender, Program::kIdentity, xposAdj, ypos,
    300                         String8(start));
    301                 *brk = ch;
    302                 start = brk;
    303                 if (strchr(kWhitespace, ch) != NULL) {
    304                     // if we broke on whitespace, skip past it
    305                     start++;
    306                 }
    307             }
    308             ypos += getScaledGlyphHeight();
    309         }
    310         free(mangle);
    311     }
    312 
    313     return ypos;
    314 }
    315 
    316 char* TextRenderer::breakString(const char* str, float maxWidth) const {
    317     // Ideally we'd do clever things like binary search.  Not bothering.
    318     ALOGV("breakString '%s' %.3f", str, maxWidth);
    319 
    320     size_t len = strlen(str);
    321     if (len == 0) {
    322         // Caller should detect this and not advance ypos.
    323         return NULL;
    324     }
    325 
    326     float stringWidth = computeScaledStringWidth(str, len);
    327     if (stringWidth <= maxWidth) {
    328         return NULL;        // trivial -- use full string
    329     }
    330 
    331     // Find the longest string that will fit.
    332     size_t goodPos = 0;
    333     for (size_t i = 0; i < len; i++) {
    334         stringWidth = computeScaledStringWidth(str, i);
    335         if (stringWidth < maxWidth) {
    336             goodPos = i;
    337         } else {
    338             break;  // too big
    339         }
    340     }
    341     if (goodPos == 0) {
    342         // space is too small to hold any glyph; output a single char
    343         ALOGW("Couldn't find a nonzero prefix that fit from '%s'", str);
    344         goodPos = 1;
    345     }
    346 
    347     // Scan back for whitespace.  If we can't find any we'll just have
    348     // an ugly mid-word break.
    349     for (size_t i = goodPos; i > 0; i--) {
    350         if (strchr(kWhitespace, str[i]) != NULL) {
    351             goodPos = i;
    352             break;
    353         }
    354     }
    355 
    356     ALOGV("goodPos=%zu for str='%s'", goodPos, str);
    357     return const_cast<char*>(str + goodPos);
    358 }
    359