Allow custom fonts in the boot animaiton zip file
Change the font format to be a 16x6 grid of characters
Bug: 29580875
Change-Id: Ia468307cb9770436e8ae865c91acda23a71bde05
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index a2d34e4..2fa1ee6 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -68,15 +68,25 @@
static const char SYSTEM_DATA_DIR_PATH[] = "/data/system";
static const char SYSTEM_TIME_DIR_NAME[] = "time";
static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time";
+static const char CLOCK_FONT_ASSET[] = "images/clock_font.png";
+static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png";
static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change";
static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change";
static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate";
static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate";
// Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00.
static const long long ACCURATE_TIME_EPOCH = 946684800000;
+static constexpr char FONT_BEGIN_CHAR = ' ';
+static constexpr char FONT_END_CHAR = '~' + 1;
+static constexpr size_t FONT_NUM_CHARS = FONT_END_CHAR - FONT_BEGIN_CHAR + 1;
+static constexpr size_t FONT_NUM_COLS = 16;
+static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS;
+static const int TEXT_CENTER_VALUE = INT_MAX;
+static const int TEXT_MISSING_VALUE = INT_MIN;
static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound";
static const int ANIM_ENTRY_NAME_MAX = 256;
+static constexpr size_t TEXT_POS_LEN_MAX = 16;
static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed";
static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason";
// bootreasons list in "system/core/bootstat/bootstat.cpp".
@@ -175,15 +185,14 @@
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
return NO_ERROR;
}
-status_t BootAnimation::initTexture(const Animation::Frame& frame)
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height)
{
- //StopWatch watch("blah");
-
SkBitmap bitmap;
- SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength());
+ SkMemoryStream stream(map->getDataPtr(), map->getDataLength());
SkImageDecoder* codec = SkImageDecoder::Factory(&stream);
if (codec != NULL) {
codec->setDitherImage(false);
@@ -196,7 +205,7 @@
// FileMap memory is never released until application exit.
// Release it now as the texture is already loaded and the memory used for
// the packed resource can be released.
- delete frame.map;
+ delete map;
// ensure we can call getPixels(). No need to call unlock, since the
// bitmap will go out of scope when we return from this method.
@@ -242,6 +251,9 @@
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ *width = w;
+ *height = h;
+
return NO_ERROR;
}
@@ -404,7 +416,6 @@
return false;
}
-
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
@@ -415,6 +426,47 @@
}
}
+bool BootAnimation::validClock(const Animation::Part& part) {
+ return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE;
+}
+
+bool parseTextCoord(const char* str, int* dest) {
+ if (strcmp("c", str) == 0) {
+ *dest = TEXT_CENTER_VALUE;
+ return true;
+ }
+
+ char* end;
+ int val = (int) strtol(str, &end, 0);
+ if (end == str || *end != '\0' || val == INT_MAX || val == INT_MIN) {
+ return false;
+ }
+ *dest = val;
+ return true;
+}
+
+// Parse two position coordinates. If only string is non-empty, treat it as the y value.
+void parsePosition(const char* str1, const char* str2, int* x, int* y) {
+ bool success = false;
+ if (strlen(str1) == 0) { // No values were specified
+ // success = false
+ } else if (strlen(str2) == 0) { // we have only one value
+ if (parseTextCoord(str1, y)) {
+ *x = TEXT_CENTER_VALUE;
+ success = true;
+ }
+ } else {
+ if (parseTextCoord(str1, x) && parseTextCoord(str2, y)) {
+ success = true;
+ }
+ }
+
+ if (!success) {
+ *x = TEXT_MISSING_VALUE;
+ *y = TEXT_MISSING_VALUE;
+ }
+}
+
// Parse a color represented as an HTML-style 'RRGGBB' string: each pair of
// characters in str is a hex number in [0, 255], which are converted to
// floating point values in the range [0.0, 1.0] and placed in the
@@ -461,24 +513,87 @@
return true;
}
-// The time glyphs are stored in a single image of height 64 pixels. Each digit is 40 pixels wide,
-// and the colon character is half that at 20 pixels. The glyph order is '0123456789:'.
-// We render 24 hour time.
-void BootAnimation::drawTime(const Texture& clockTex, const int yPos) {
- static constexpr char TIME_FORMAT[] = "%H:%M";
- static constexpr int TIME_LENGTH = sizeof(TIME_FORMAT);
+// The font image should be a 96x2 array of character images. The
+// columns are the printable ASCII characters 0x20 - 0x7f. The
+// top row is regular text; the bottom row is bold.
+status_t BootAnimation::initFont(Font* font, const char* fallback) {
+ status_t status = NO_ERROR;
- static constexpr int DIGIT_HEIGHT = 64;
- static constexpr int DIGIT_WIDTH = 40;
- static constexpr int COLON_WIDTH = DIGIT_WIDTH / 2;
- static constexpr int TIME_WIDTH = (DIGIT_WIDTH * 4) + COLON_WIDTH;
+ if (font->map != nullptr) {
+ glGenTextures(1, &font->texture.name);
+ glBindTexture(GL_TEXTURE_2D, font->texture.name);
- if (clockTex.h < DIGIT_HEIGHT || clockTex.w < (10 * DIGIT_WIDTH + COLON_WIDTH)) {
- ALOGE("Clock texture is too small; abandoning boot animation clock");
- mClockEnabled = false;
- return;
+ status = initTexture(font->map, &font->texture.w, &font->texture.h);
+
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ } else if (fallback != nullptr) {
+ status = initTexture(&font->texture, mAssets, fallback);
+ } else {
+ return NO_INIT;
}
+ if (status == NO_ERROR) {
+ font->char_width = font->texture.w / FONT_NUM_COLS;
+ font->char_height = font->texture.h / FONT_NUM_ROWS / 2; // There are bold and regular rows
+ }
+
+ return status;
+}
+
+void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
+ glEnable(GL_BLEND); // Allow us to draw on top of the animation
+ glBindTexture(GL_TEXTURE_2D, font.texture.name);
+
+ const int len = strlen(str);
+ const int strWidth = font.char_width * len;
+
+ if (*x == TEXT_CENTER_VALUE) {
+ *x = (mWidth - strWidth) / 2;
+ } else if (*x < 0) {
+ *x = mWidth + *x - strWidth;
+ }
+ if (*y == TEXT_CENTER_VALUE) {
+ *y = (mHeight - font.char_height) / 2;
+ } else if (*y < 0) {
+ *y = mHeight + *y - font.char_height;
+ }
+
+ int cropRect[4] = { 0, 0, font.char_width, -font.char_height };
+
+ for (int i = 0; i < len; i++) {
+ char c = str[i];
+
+ if (c < FONT_BEGIN_CHAR || c > FONT_END_CHAR) {
+ c = '?';
+ }
+
+ // Crop the texture to only the pixels in the current glyph
+ const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters
+ const int row = charPos / FONT_NUM_COLS;
+ const int col = charPos % FONT_NUM_COLS;
+ cropRect[0] = col * font.char_width; // Left of column
+ cropRect[1] = row * font.char_height * 2; // Top of row
+ // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line
+ cropRect[1] += bold ? 2 * font.char_height : font.char_height;
+ glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
+
+ glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height);
+
+ *x += font.char_width;
+ }
+
+ glDisable(GL_BLEND); // Return to the animation's default behaviour
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+// We render 24 hour time.
+void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) {
+ static constexpr char TIME_FORMAT[] = "%H:%M";
+ static constexpr int TIME_LENGTH = 6;
+
time_t rawtime;
time(&rawtime);
struct tm* timeInfo = localtime(&rawtime);
@@ -492,36 +607,9 @@
return;
}
- glEnable(GL_BLEND); // Allow us to draw on top of the animation
- glBindTexture(GL_TEXTURE_2D, clockTex.name);
-
- int xPos = (mWidth - TIME_WIDTH) / 2;
- int cropRect[4] = { 0, DIGIT_HEIGHT, DIGIT_WIDTH, -DIGIT_HEIGHT };
-
- for (int i = 0; i < TIME_LENGTH - 1; i++) {
- char c = timeBuff[i];
- int width = DIGIT_WIDTH;
- int pos = c - '0'; // Position in the character list
- if (pos < 0 || pos > 10) {
- continue;
- }
- if (c == ':') {
- width = COLON_WIDTH;
- }
-
- // Crop the texture to only the pixels in the current glyph
- int left = pos * DIGIT_WIDTH;
- cropRect[0] = left;
- cropRect[2] = width;
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
-
- glDrawTexiOES(xPos, yPos, 0, width, DIGIT_HEIGHT);
-
- xPos += width;
- }
-
- glDisable(GL_BLEND); // Return to the animation's default behaviour
- glBindTexture(GL_TEXTURE_2D, 0);
+ int x = xPos;
+ int y = yPos;
+ drawText(timeBuff, font, false, &x, &y);
}
bool BootAnimation::parseAnimationDesc(Animation& animation)
@@ -544,9 +632,10 @@
int height = 0;
int count = 0;
int pause = 0;
- int clockPosY = -1;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
+ char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
+ char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
char pathType;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
@@ -554,15 +643,15 @@
animation.width = width;
animation.height = height;
animation.fps = fps;
- } else if (sscanf(l, " %c %d %d %s #%6s %d",
- &pathType, &count, &pause, path, color, &clockPosY) >= 4) {
- // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPosY=%d", pathType, count, pause, path, color, clockPosY);
+ } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s",
+ &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
+ //ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
+ // pathType, count, pause, path, color, clockPos1, clockPos2);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
part.pause = pause;
part.path = path;
- part.clockPosY = clockPosY;
part.audioData = NULL;
part.animation = NULL;
if (!parseColor(color, part.backgroundColor)) {
@@ -571,6 +660,7 @@
part.backgroundColor[1] = 0.0f;
part.backgroundColor[2] = 0.0f;
}
+ parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY);
animation.parts.add(part);
}
else if (strcmp(l, "$SYSTEM") == 0) {
@@ -614,6 +704,14 @@
const String8 path(entryName.getPathDir());
const String8 leaf(entryName.getPathLeaf());
if (leaf.size() > 0) {
+ if (entryName == CLOCK_FONT_ZIP_NAME) {
+ FileMap* map = zip->createEntryFileMap(entry);
+ if (map) {
+ animation.clockFont.map = map;
+ }
+ continue;
+ }
+
for (size_t j = 0; j < pcount; j++) {
if (path == animation.parts[j].path) {
uint16_t method;
@@ -698,7 +796,7 @@
bool anyPartHasClock = false;
for (size_t i=0; i < animation->parts.size(); i++) {
- if(animation->parts[i].clockPosY >= 0) {
+ if(validClock(animation->parts[i])) {
anyPartHasClock = true;
break;
}
@@ -736,10 +834,11 @@
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- bool clockTextureInitialized = false;
+ bool clockFontInitialized = false;
if (mClockEnabled) {
- clockTextureInitialized = (initTexture(&mClock, mAssets, "images/clock64.png") == NO_ERROR);
- mClockEnabled = clockTextureInitialized;
+ clockFontInitialized =
+ (initFont(&animation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR);
+ mClockEnabled = clockFontInitialized;
}
if (mClockEnabled && !updateIsTimeAccurate()) {
@@ -756,8 +855,8 @@
releaseAnimation(animation);
- if (clockTextureInitialized) {
- glDeleteTextures(1, &mClock.name);
+ if (clockFontInitialized) {
+ glDeleteTextures(1, &animation->clockFont.texture.name);
}
return false;
@@ -813,7 +912,8 @@
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
- initTexture(frame);
+ int w, h;
+ initTexture(frame.map, &w, &h);
}
const int xc = animationX + frame.trimX;
@@ -835,8 +935,8 @@
// which is equivalent to mHeight - (yc + frame.trimHeight)
glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
0, frame.trimWidth, frame.trimHeight);
- if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
- drawTime(mClock, part.clockPosY);
+ if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
+ drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
eglSwapBuffers(mDisplay, mSurface);
@@ -915,6 +1015,7 @@
Animation *animation = new Animation;
animation->fileName = fn;
animation->zip = zip;
+ animation->clockFont.map = nullptr;
mLoadedFiles.add(animation->fileName);
parseAnimationDesc(*animation);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index fd497a3..42759f1 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -74,6 +74,13 @@
GLuint name;
};
+ struct Font {
+ FileMap* map;
+ Texture texture;
+ int char_width;
+ int char_height;
+ };
+
struct Animation {
struct Frame {
String8 name;
@@ -90,8 +97,12 @@
struct Part {
int count; // The number of times this part should repeat, 0 for infinite
int pause; // The number of frames to pause for at the end of this part
- int clockPosY; // The y position of the clock, in pixels, from the bottom of the
- // display (the clock is centred horizontally). -1 to disable the clock
+ int clockPosX; // The x position of the clock, in pixels. Positive values offset from
+ // the left of the screen, negative values offset from the right.
+ int clockPosY; // The y position of the clock, in pixels. Positive values offset from
+ // the bottom of the screen, negative values offset from the top.
+ // If either of the above are INT_MIN the clock is disabled, if INT_MAX
+ // the clock is centred on that axis.
String8 path;
String8 trimData;
SortedVector<Frame> frames;
@@ -108,13 +119,17 @@
String8 audioConf;
String8 fileName;
ZipFileRO* zip;
+ Font clockFont;
};
status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
- status_t initTexture(const Animation::Frame& frame);
+ status_t initTexture(FileMap* map, int* width, int* height);
+ status_t initFont(Font* font, const char* fallback);
bool android();
bool movie();
- void drawTime(const Texture& clockTex, const int yPos);
+ void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
+ void drawClock(const Font& font, const int xPos, const int yPos);
+ bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
void releaseAnimation(Animation*) const;
@@ -127,7 +142,6 @@
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
Texture mAndroid[2];
- Texture mClock;
int mWidth;
int mHeight;
bool mUseNpotTextures = false;
diff --git a/core/res/assets/images/clock64.png b/core/res/assets/images/clock64.png
deleted file mode 100644
index 2e01e38..0000000
--- a/core/res/assets/images/clock64.png
+++ /dev/null
Binary files differ
diff --git a/core/res/assets/images/clock_font.png b/core/res/assets/images/clock_font.png
new file mode 100644
index 0000000..be927ae
--- /dev/null
+++ b/core/res/assets/images/clock_font.png
Binary files differ