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