Merge "Support trimmed images in BootAnimation" into nyc-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
656b9db5fb
@ -596,15 +596,15 @@ bool BootAnimation::preloadZip(Animation& animation)
|
|||||||
// read all the data structures
|
// read all the data structures
|
||||||
const size_t pcount = animation.parts.size();
|
const size_t pcount = animation.parts.size();
|
||||||
void *cookie = NULL;
|
void *cookie = NULL;
|
||||||
ZipFileRO* mZip = animation.zip;
|
ZipFileRO* zip = animation.zip;
|
||||||
if (!mZip->startIteration(&cookie)) {
|
if (!zip->startIteration(&cookie)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipEntryRO entry;
|
ZipEntryRO entry;
|
||||||
char name[ANIM_ENTRY_NAME_MAX];
|
char name[ANIM_ENTRY_NAME_MAX];
|
||||||
while ((entry = mZip->nextEntry(cookie)) != NULL) {
|
while ((entry = zip->nextEntry(cookie)) != NULL) {
|
||||||
const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
|
const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
|
||||||
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
|
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
|
||||||
ALOGE("Error fetching entry file name");
|
ALOGE("Error fetching entry file name");
|
||||||
continue;
|
continue;
|
||||||
@ -614,22 +614,29 @@ bool BootAnimation::preloadZip(Animation& animation)
|
|||||||
const String8 path(entryName.getPathDir());
|
const String8 path(entryName.getPathDir());
|
||||||
const String8 leaf(entryName.getPathLeaf());
|
const String8 leaf(entryName.getPathLeaf());
|
||||||
if (leaf.size() > 0) {
|
if (leaf.size() > 0) {
|
||||||
for (size_t j=0 ; j<pcount ; j++) {
|
for (size_t j = 0; j < pcount; j++) {
|
||||||
if (path == animation.parts[j].path) {
|
if (path == animation.parts[j].path) {
|
||||||
uint16_t method;
|
uint16_t method;
|
||||||
// supports only stored png files
|
// supports only stored png files
|
||||||
if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
|
if (zip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
|
||||||
if (method == ZipFileRO::kCompressStored) {
|
if (method == ZipFileRO::kCompressStored) {
|
||||||
FileMap* map = mZip->createEntryFileMap(entry);
|
FileMap* map = zip->createEntryFileMap(entry);
|
||||||
if (map) {
|
if (map) {
|
||||||
Animation::Part& part(animation.parts.editItemAt(j));
|
Animation::Part& part(animation.parts.editItemAt(j));
|
||||||
if (leaf == "audio.wav") {
|
if (leaf == "audio.wav") {
|
||||||
// a part may have at most one audio file
|
// a part may have at most one audio file
|
||||||
part.audioFile = map;
|
part.audioFile = map;
|
||||||
|
} else if (leaf == "trim.txt") {
|
||||||
|
part.trimData.setTo((char const*)map->getDataPtr(),
|
||||||
|
map->getDataLength());
|
||||||
} else {
|
} else {
|
||||||
Animation::Frame frame;
|
Animation::Frame frame;
|
||||||
frame.name = leaf;
|
frame.name = leaf;
|
||||||
frame.map = map;
|
frame.map = map;
|
||||||
|
frame.trimWidth = animation.width;
|
||||||
|
frame.trimHeight = animation.height;
|
||||||
|
frame.trimX = 0;
|
||||||
|
frame.trimY = 0;
|
||||||
part.frames.add(frame);
|
part.frames.add(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -640,7 +647,33 @@ bool BootAnimation::preloadZip(Animation& animation)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mZip->endIteration(cookie);
|
// If there is trimData present, override the positioning defaults.
|
||||||
|
for (Animation::Part& part : animation.parts) {
|
||||||
|
const char* trimDataStr = part.trimData.string();
|
||||||
|
for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) {
|
||||||
|
const char* endl = strstr(trimDataStr, "\n");
|
||||||
|
// No more trimData for this part.
|
||||||
|
if (endl == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String8 line(trimDataStr, endl - trimDataStr);
|
||||||
|
const char* lineStr = line.string();
|
||||||
|
trimDataStr = ++endl;
|
||||||
|
int width = 0, height = 0, x = 0, y = 0;
|
||||||
|
if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) {
|
||||||
|
Animation::Frame& frame(part.frames.editItemAt(frameIdx));
|
||||||
|
frame.trimWidth = width;
|
||||||
|
frame.trimHeight = height;
|
||||||
|
frame.trimX = x;
|
||||||
|
frame.trimY = y;
|
||||||
|
} else {
|
||||||
|
ALOGE("Error parsing trim.txt, line: %s", lineStr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zip->endIteration(cookie);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -707,12 +740,9 @@ bool BootAnimation::movie()
|
|||||||
bool BootAnimation::playAnimation(const Animation& animation)
|
bool BootAnimation::playAnimation(const Animation& animation)
|
||||||
{
|
{
|
||||||
const size_t pcount = animation.parts.size();
|
const size_t pcount = animation.parts.size();
|
||||||
const int xc = (mWidth - animation.width) / 2;
|
|
||||||
const int yc = ((mHeight - animation.height) / 2);
|
|
||||||
nsecs_t frameDuration = s2ns(1) / animation.fps;
|
nsecs_t frameDuration = s2ns(1) / animation.fps;
|
||||||
|
const int animationX = (mWidth - animation.width) / 2;
|
||||||
Region clearReg(Rect(mWidth, mHeight));
|
const int animationY = (mHeight - animation.height) / 2;
|
||||||
clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
|
|
||||||
|
|
||||||
for (size_t i=0 ; i<pcount ; i++) {
|
for (size_t i=0 ; i<pcount ; i++) {
|
||||||
const Animation::Part& part(animation.parts[i]);
|
const Animation::Part& part(animation.parts[i]);
|
||||||
@ -759,22 +789,25 @@ bool BootAnimation::playAnimation(const Animation& animation)
|
|||||||
initTexture(frame);
|
initTexture(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int xc = animationX + frame.trimX;
|
||||||
|
const int yc = animationY + frame.trimY;
|
||||||
|
Region clearReg(Rect(mWidth, mHeight));
|
||||||
|
clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
|
||||||
if (!clearReg.isEmpty()) {
|
if (!clearReg.isEmpty()) {
|
||||||
Region::const_iterator head(clearReg.begin());
|
Region::const_iterator head(clearReg.begin());
|
||||||
Region::const_iterator tail(clearReg.end());
|
Region::const_iterator tail(clearReg.end());
|
||||||
glEnable(GL_SCISSOR_TEST);
|
glEnable(GL_SCISSOR_TEST);
|
||||||
while (head != tail) {
|
while (head != tail) {
|
||||||
const Rect& r2(*head++);
|
const Rect& r2(*head++);
|
||||||
glScissor(r2.left, mHeight - r2.bottom,
|
glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
|
||||||
r2.width(), r2.height());
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
glDisable(GL_SCISSOR_TEST);
|
glDisable(GL_SCISSOR_TEST);
|
||||||
}
|
}
|
||||||
// specify the y center as ceiling((mHeight - animation.height) / 2)
|
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
|
||||||
// which is equivalent to mHeight - (yc + animation.height)
|
// which is equivalent to mHeight - (yc + frame.trimHeight)
|
||||||
glDrawTexiOES(xc, mHeight - (yc + animation.height),
|
glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
|
||||||
0, animation.width, animation.height);
|
0, frame.trimWidth, frame.trimHeight);
|
||||||
if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
|
if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) {
|
||||||
drawTime(mClock, part.clockPosY);
|
drawTime(mClock, part.clockPosY);
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,10 @@ private:
|
|||||||
struct Frame {
|
struct Frame {
|
||||||
String8 name;
|
String8 name;
|
||||||
FileMap* map;
|
FileMap* map;
|
||||||
|
int trimX;
|
||||||
|
int trimY;
|
||||||
|
int trimWidth;
|
||||||
|
int trimHeight;
|
||||||
mutable GLuint tid;
|
mutable GLuint tid;
|
||||||
bool operator < (const Frame& rhs) const {
|
bool operator < (const Frame& rhs) const {
|
||||||
return name < rhs.name;
|
return name < rhs.name;
|
||||||
@ -90,6 +94,7 @@ private:
|
|||||||
int clockPosY; // The y position of the clock, in pixels, from the bottom of the
|
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
|
// display (the clock is centred horizontally). -1 to disable the clock
|
||||||
String8 path;
|
String8 path;
|
||||||
|
String8 trimData;
|
||||||
SortedVector<Frame> frames;
|
SortedVector<Frame> frames;
|
||||||
bool playUntilComplete;
|
bool playUntilComplete;
|
||||||
float backgroundColor[3];
|
float backgroundColor[3];
|
||||||
|
127
cmds/bootanimation/FORMAT.md
Normal file
127
cmds/bootanimation/FORMAT.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# bootanimation format
|
||||||
|
|
||||||
|
## zipfile paths
|
||||||
|
|
||||||
|
The system selects a boot animation zipfile from the following locations, in order:
|
||||||
|
|
||||||
|
/system/media/bootanimation-encrypted.zip (if getprop("vold.decrypt") = '1')
|
||||||
|
/system/media/bootanimation.zip
|
||||||
|
/oem/media/bootanimation.zip
|
||||||
|
|
||||||
|
## zipfile layout
|
||||||
|
|
||||||
|
The `bootanimation.zip` archive file includes:
|
||||||
|
|
||||||
|
desc.txt - a text file
|
||||||
|
part0 \
|
||||||
|
part1 \ directories full of PNG frames
|
||||||
|
... /
|
||||||
|
partN /
|
||||||
|
|
||||||
|
## desc.txt format
|
||||||
|
|
||||||
|
The first line defines the general parameters of the animation:
|
||||||
|
|
||||||
|
WIDTH HEIGHT FPS
|
||||||
|
|
||||||
|
* **WIDTH:** animation width (pixels)
|
||||||
|
* **HEIGHT:** animation height (pixels)
|
||||||
|
* **FPS:** frames per second, e.g. 60
|
||||||
|
|
||||||
|
It is followed by a number of rows of the form:
|
||||||
|
|
||||||
|
TYPE COUNT PAUSE PATH [#RGBHEX CLOCK]
|
||||||
|
|
||||||
|
* **TYPE:** a single char indicating what type of animation segment this is:
|
||||||
|
+ `p` -- this part will play unless interrupted by the end of the boot
|
||||||
|
+ `c` -- this part will play to completion, no matter what
|
||||||
|
* **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete
|
||||||
|
* **PAUSE:** number of FRAMES to delay after this part ends
|
||||||
|
* **PATH:** directory in which to find the frames for this part (e.g. `part0`)
|
||||||
|
* **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`
|
||||||
|
* **CLOCK:** _(OPTIONAL)_ the y-coordinate at which to draw the current time (for watches)
|
||||||
|
|
||||||
|
There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip`
|
||||||
|
and plays that.
|
||||||
|
|
||||||
|
## loading and playing frames
|
||||||
|
|
||||||
|
Each part is scanned and loaded directly from the zip archive. Within a part directory, every file
|
||||||
|
(except `trim.txt` and `audio.wav`; see next sections) is expected to be a PNG file that represents
|
||||||
|
one frame in that part (at the specified resolution). For this reason it is important that frames be
|
||||||
|
named sequentially (e.g. `part000.png`, `part001.png`, ...) and added to the zip archive in that
|
||||||
|
order.
|
||||||
|
|
||||||
|
## trim.txt
|
||||||
|
|
||||||
|
To save on memory, textures may be trimmed by their background color. trim.txt sequentially lists
|
||||||
|
the trim output for each frame in its directory, so the frames may be properly positioned.
|
||||||
|
Output should be of the form: `WxH+X+Y`. Example:
|
||||||
|
|
||||||
|
713x165+388+914
|
||||||
|
708x152+388+912
|
||||||
|
707x139+388+911
|
||||||
|
649x92+388+910
|
||||||
|
|
||||||
|
If the file is not present, each frame is assumed to be the same size as the animation.
|
||||||
|
|
||||||
|
## audio.wav
|
||||||
|
|
||||||
|
Each part may optionally play a `wav` sample when it starts. To enable this for an animation,
|
||||||
|
you must also include a `audio_conf.txt` file in the ZIP archive. Its format is as follows:
|
||||||
|
|
||||||
|
card=<ALSA card number>
|
||||||
|
device=<ALSA device number>
|
||||||
|
period_size=<period size>
|
||||||
|
period_count=<period count>
|
||||||
|
|
||||||
|
This header is followed by zero or more mixer settings, each with the format:
|
||||||
|
|
||||||
|
mixer "<name>" = <value list>
|
||||||
|
|
||||||
|
Here's an example `audio_conf.txt` from Shamu:
|
||||||
|
|
||||||
|
card=0
|
||||||
|
device=15
|
||||||
|
period_size=1024
|
||||||
|
period_count=4
|
||||||
|
|
||||||
|
mixer "QUAT_MI2S_RX Audio Mixer MultiMedia5" = 1
|
||||||
|
mixer "Playback Channel Map" = 0 220 157 195 0 0 0 0
|
||||||
|
mixer "QUAT_MI2S_RX Channels" = Two
|
||||||
|
mixer "BOOST_STUB Right Mixer right" = 1
|
||||||
|
mixer "BOOST_STUB Left Mixer left" = 1
|
||||||
|
mixer "Compress Playback 9 Volume" = 80 80
|
||||||
|
|
||||||
|
You will probably need to get these mixer names and values out of `audio_platform_info.xml`
|
||||||
|
and `mixer_paths.xml` for your device.
|
||||||
|
|
||||||
|
## exiting
|
||||||
|
|
||||||
|
The system will end the boot animation (first completing any incomplete or even entirely unplayed
|
||||||
|
parts that are of type `c`) when the system is finished booting. (This is accomplished by setting
|
||||||
|
the system property `service.bootanim.exit` to a nonzero string.)
|
||||||
|
|
||||||
|
## protips
|
||||||
|
|
||||||
|
### PNG compression
|
||||||
|
|
||||||
|
Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.:
|
||||||
|
|
||||||
|
for fn in *.png ; do
|
||||||
|
zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn}
|
||||||
|
# or: pngcrush -q ....
|
||||||
|
done
|
||||||
|
|
||||||
|
Some animations benefit from being reduced to 256 colors:
|
||||||
|
|
||||||
|
pngquant --force --ext .png *.png
|
||||||
|
# alternatively: mogrify -colors 256 anim-tmp/*/*.png
|
||||||
|
|
||||||
|
### creating the ZIP archive
|
||||||
|
|
||||||
|
cd <path-to-pieces>
|
||||||
|
zip -0qry -i \*.txt \*.png \*.wav @ ../bootanimation.zip *.txt part*
|
||||||
|
|
||||||
|
Note that the ZIP archive is not actually compressed! The PNG files are already as compressed
|
||||||
|
as they can reasonably get, and there is unlikely to be any redundancy between files.
|
Reference in New Issue
Block a user