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
|
||||
const size_t pcount = animation.parts.size();
|
||||
void *cookie = NULL;
|
||||
ZipFileRO* mZip = animation.zip;
|
||||
if (!mZip->startIteration(&cookie)) {
|
||||
ZipFileRO* zip = animation.zip;
|
||||
if (!zip->startIteration(&cookie)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ZipEntryRO entry;
|
||||
char name[ANIM_ENTRY_NAME_MAX];
|
||||
while ((entry = mZip->nextEntry(cookie)) != NULL) {
|
||||
const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
|
||||
while ((entry = zip->nextEntry(cookie)) != NULL) {
|
||||
const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
|
||||
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
|
||||
ALOGE("Error fetching entry file name");
|
||||
continue;
|
||||
@ -614,22 +614,29 @@ bool BootAnimation::preloadZip(Animation& animation)
|
||||
const String8 path(entryName.getPathDir());
|
||||
const String8 leaf(entryName.getPathLeaf());
|
||||
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) {
|
||||
uint16_t method;
|
||||
// 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) {
|
||||
FileMap* map = mZip->createEntryFileMap(entry);
|
||||
FileMap* map = zip->createEntryFileMap(entry);
|
||||
if (map) {
|
||||
Animation::Part& part(animation.parts.editItemAt(j));
|
||||
if (leaf == "audio.wav") {
|
||||
// a part may have at most one audio file
|
||||
part.audioFile = map;
|
||||
} else if (leaf == "trim.txt") {
|
||||
part.trimData.setTo((char const*)map->getDataPtr(),
|
||||
map->getDataLength());
|
||||
} else {
|
||||
Animation::Frame frame;
|
||||
frame.name = leaf;
|
||||
frame.map = map;
|
||||
frame.trimWidth = animation.width;
|
||||
frame.trimHeight = animation.height;
|
||||
frame.trimX = 0;
|
||||
frame.trimY = 0;
|
||||
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;
|
||||
}
|
||||
@ -707,12 +740,9 @@ bool BootAnimation::movie()
|
||||
bool BootAnimation::playAnimation(const Animation& animation)
|
||||
{
|
||||
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;
|
||||
|
||||
Region clearReg(Rect(mWidth, mHeight));
|
||||
clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
|
||||
const int animationX = (mWidth - animation.width) / 2;
|
||||
const int animationY = (mHeight - animation.height) / 2;
|
||||
|
||||
for (size_t i=0 ; i<pcount ; i++) {
|
||||
const Animation::Part& part(animation.parts[i]);
|
||||
@ -759,22 +789,25 @@ bool BootAnimation::playAnimation(const Animation& animation)
|
||||
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()) {
|
||||
Region::const_iterator head(clearReg.begin());
|
||||
Region::const_iterator tail(clearReg.end());
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
while (head != tail) {
|
||||
const Rect& r2(*head++);
|
||||
glScissor(r2.left, mHeight - r2.bottom,
|
||||
r2.width(), r2.height());
|
||||
glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
// specify the y center as ceiling((mHeight - animation.height) / 2)
|
||||
// which is equivalent to mHeight - (yc + animation.height)
|
||||
glDrawTexiOES(xc, mHeight - (yc + animation.height),
|
||||
0, animation.width, animation.height);
|
||||
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
|
||||
// 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);
|
||||
}
|
||||
|
@ -79,6 +79,10 @@ private:
|
||||
struct Frame {
|
||||
String8 name;
|
||||
FileMap* map;
|
||||
int trimX;
|
||||
int trimY;
|
||||
int trimWidth;
|
||||
int trimHeight;
|
||||
mutable GLuint tid;
|
||||
bool operator < (const Frame& rhs) const {
|
||||
return name < rhs.name;
|
||||
@ -90,6 +94,7 @@ private:
|
||||
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
|
||||
String8 path;
|
||||
String8 trimData;
|
||||
SortedVector<Frame> frames;
|
||||
bool playUntilComplete;
|
||||
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