OSDN Git Service

Support trimmed images in BootAnimation
authorGeoffrey Pitsch <gpitsch@google.com>
Mon, 27 Jun 2016 21:14:30 +0000 (17:14 -0400)
committerGeoffrey Pitsch <gpitsch@google.com>
Wed, 29 Jun 2016 13:35:21 +0000 (09:35 -0400)
Each frame directory may optionally contain a `trim.txt` to specify
how the image was trimmed relative to the animation's full size.
See FORMAT.md for more details.

Bug: 29055299
Change-Id: I7a291e9a52b4bbe50a29c519f6a4d0e2cc0389d7

cmds/bootanimation/BootAnimation.cpp
cmds/bootanimation/BootAnimation.h
cmds/bootanimation/FORMAT.md [new file with mode: 0644]

index e8fcd3b..e849f4b 100644 (file)
@@ -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);
                 }
index 1c3d53a..a093c9b 100644 (file)
@@ -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];
diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md
new file mode 100644 (file)
index 0000000..e4c52f7
--- /dev/null
@@ -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.