Loading cmds/bootanimation/BootAnimation.cpp +52 −19 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -618,18 +618,25 @@ bool BootAnimation::preloadZip(Animation& animation) 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); } } Loading @@ -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; } Loading Loading @@ -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]); Loading Loading @@ -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); } Loading cmds/bootanimation/BootAnimation.h +5 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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]; Loading cmds/bootanimation/FORMAT.md 0 → 100644 +127 −0 Original line number Diff line number Diff line # 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. Loading
cmds/bootanimation/BootAnimation.cpp +52 −19 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -618,18 +618,25 @@ bool BootAnimation::preloadZip(Animation& animation) 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); } } Loading @@ -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; } Loading Loading @@ -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]); Loading Loading @@ -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); } Loading
cmds/bootanimation/BootAnimation.h +5 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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]; Loading
cmds/bootanimation/FORMAT.md 0 → 100644 +127 −0 Original line number Diff line number Diff line # 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.