Bump to version 1.22.1
[platform/upstream/busybox.git] / miscutils / fbsplash.c
index f8289c3..12a77b7 100644 (file)
@@ -1,9 +1,8 @@
 /* vi: set sw=4 ts=4: */
 /*
- * Copyright (C) 2008 Michele Sanges <michele.sanges@otomelara.it>,
- * <michele.sanges@gmail.it>
+ * Copyright (C) 2008 Michele Sanges <michele.sanges@gmail.com>
  *
- * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  *
  * Usage:
  * - use kernel option 'vga=xxx' or otherwise enable framebuffer device.
  *   "exit" (or just close fifo) - well you guessed it.
  */
 
+//usage:#define fbsplash_trivial_usage
+//usage:       "-s IMGFILE [-c] [-d DEV] [-i INIFILE] [-f CMD]"
+//usage:#define fbsplash_full_usage "\n\n"
+//usage:       "       -s      Image"
+//usage:     "\n       -c      Hide cursor"
+//usage:     "\n       -d      Framebuffer device (default /dev/fb0)"
+//usage:     "\n       -i      Config file (var=value):"
+//usage:     "\n                       BAR_LEFT,BAR_TOP,BAR_WIDTH,BAR_HEIGHT"
+//usage:     "\n                       BAR_R,BAR_G,BAR_B"
+//usage:     "\n       -f      Control pipe (else exit after drawing image)"
+//usage:     "\n                       commands: 'NN' (% for progress bar) or 'exit'"
+
 #include "libbb.h"
 #include <linux/fb.h>
 
 /* If you want logging messages on /tmp/fbsplash.log... */
 #define DEBUG 0
 
-#define BYTES_PER_PIXEL 2
-
-typedef unsigned short DATA;
-
 struct globals {
 #if DEBUG
        bool bdebug_messages;   // enable/disable logging
@@ -42,6 +49,11 @@ struct globals {
        const char *image_filename;
        struct fb_var_screeninfo scr_var;
        struct fb_fix_screeninfo scr_fix;
+       unsigned bytes_per_pixel;
+       // cached (8 - scr_var.COLOR.length):
+       unsigned red_shift;
+       unsigned green_shift;
+       unsigned blue_shift;
 };
 #define G (*ptr_to_globals)
 #define INIT_G() do { \
@@ -66,9 +78,46 @@ struct globals {
 #define DEBUG_MESSAGE(...) ((void)0)
 #endif
 
+/**
+ * Configure palette for RGB:332
+ */
+static void fb_setpal(int fd)
+{
+       struct fb_cmap cmap;
+       /* fb colors are 16 bit */
+       unsigned short red[256], green[256], blue[256];
+       unsigned i;
+
+       /* RGB:332 */
+       for (i = 0; i < 256; i++) {
+               /* Color is encoded in pixel value as rrrgggbb.
+                * 3-bit color is mapped to 16-bit one as:
+                * 000 -> 00000000 00000000
+                * 001 -> 00100100 10010010
+                * ...
+                * 011 -> 01101101 10110110
+                * 100 -> 10010010 01001001
+                * ...
+                * 111 -> 11111111 11111111
+                */
+               red[i]   = (( i >> 5       ) * 0x9249) >> 2; // rrr * 00 10010010 01001001 >> 2
+               green[i] = (((i >> 2) & 0x7) * 0x9249) >> 2; // ggg * 00 10010010 01001001 >> 2
+               /* 2-bit color is easier: */
+               blue[i]  =  ( i       & 0x3) * 0x5555; // bb * 01010101 01010101
+       }
+
+       cmap.start = 0;
+       cmap.len   = 256;
+       cmap.red   = red;
+       cmap.green = green;
+       cmap.blue  = blue;
+       cmap.transp = 0;
+
+       xioctl(fd, FBIOPUTCMAP, &cmap);
+}
 
 /**
- *     Open and initialize the framebuffer device
+ * Open and initialize the framebuffer device
  * \param *strfb_device pointer to framebuffer device
  */
 static void fb_open(const char *strfb_device)
@@ -79,59 +128,133 @@ static void fb_open(const char *strfb_device)
        xioctl(fbfd, FBIOGET_VSCREENINFO, &G.scr_var);
        xioctl(fbfd, FBIOGET_FSCREENINFO, &G.scr_fix);
 
-       if (G.scr_var.bits_per_pixel != 16)
-               bb_error_msg_and_die("only 16 bpp is supported");
+       switch (G.scr_var.bits_per_pixel) {
+       case 8:
+               fb_setpal(fbfd);
+               break;
+
+       case 16:
+       case 24:
+       case 32:
+               break;
+
+       default:
+               bb_error_msg_and_die("unsupported %u bpp", (int)G.scr_var.bits_per_pixel);
+               break;
+       }
+
+       G.red_shift   = 8 - G.scr_var.red.length;
+       G.green_shift = 8 - G.scr_var.green.length;
+       G.blue_shift  = 8 - G.scr_var.blue.length;
+       G.bytes_per_pixel = (G.scr_var.bits_per_pixel + 7) >> 3;
 
        // map the device in memory
        G.addr = mmap(NULL,
-                       G.scr_var.xres * G.scr_var.yres
-                       * BYTES_PER_PIXEL /*(G.scr_var.bits_per_pixel / 8)*/ ,
+                       G.scr_var.yres * G.scr_fix.line_length,
                        PROT_WRITE, MAP_SHARED, fbfd, 0);
        if (G.addr == MAP_FAILED)
-               bb_perror_msg_and_die("can't mmap %s", strfb_device);
+               bb_perror_msg_and_die("mmap");
+
+       // point to the start of the visible screen
+       G.addr += G.scr_var.yoffset * G.scr_fix.line_length + G.scr_var.xoffset * G.bytes_per_pixel;
        close(fbfd);
 }
 
 
 /**
- *     Draw hollow rectangle on framebuffer
+ * Return pixel value of the passed RGB color.
+ * This is performance critical fn.
+ */
+static unsigned fb_pixel_value(unsigned r, unsigned g, unsigned b)
+{
+       /* We assume that the r,g,b values are <= 255 */
+
+       if (G.bytes_per_pixel == 1) {
+               r = r        & 0xe0; // 3-bit red
+               g = (g >> 3) & 0x1c; // 3-bit green
+               b =  b >> 6;         // 2-bit blue
+               return r + g + b;
+       }
+       if (G.bytes_per_pixel == 2) {
+               // ARM PL110 on Integrator/CP has RGBA5551 bit arrangement.
+               // We want to support bit locations like that.
+               //
+               // First shift out unused bits
+               r = r >> G.red_shift;
+               g = g >> G.green_shift;
+               b = b >> G.blue_shift;
+               // Then shift the remaining bits to their offset
+               return (r << G.scr_var.red.offset) +
+                       (g << G.scr_var.green.offset) +
+                       (b << G.scr_var.blue.offset);
+       }
+       // RGB 888
+       return b + (g << 8) + (r << 16);
+}
+
+/**
+ * Draw pixel on framebuffer
+ */
+static void fb_write_pixel(unsigned char *addr, unsigned pixel)
+{
+       switch (G.bytes_per_pixel) {
+       case 1:
+               *addr = pixel;
+               break;
+       case 2:
+               *(uint16_t *)addr = pixel;
+               break;
+       case 4:
+               *(uint32_t *)addr = pixel;
+               break;
+       default: // 24 bits per pixel
+               addr[0] = pixel;
+               addr[1] = pixel >> 8;
+               addr[2] = pixel >> 16;
+       }
+}
+
+
+/**
+ * Draw hollow rectangle on framebuffer
  */
 static void fb_drawrectangle(void)
 {
        int cnt;
-       DATA thispix;
-       DATA *ptr1, *ptr2;
+       unsigned thispix;
+       unsigned char *ptr1, *ptr2;
        unsigned char nred = G.nbar_colr/2;
        unsigned char ngreen =  G.nbar_colg/2;
        unsigned char nblue = G.nbar_colb/2;
 
-       nred   >>= 3;  // 5-bit red
-       ngreen >>= 2;  // 6-bit green
-       nblue  >>= 3;  // 5-bit blue
-       thispix = nblue + (ngreen << 5) + (nred << (5+6));
+       thispix = fb_pixel_value(nred, ngreen, nblue);
 
        // horizontal lines
-       ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
-       ptr2 = (DATA*)(G.addr + ((G.nbar_posy + G.nbar_height - 1) * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
+       ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
+       ptr2 = G.addr + (G.nbar_posy + G.nbar_height - 1) * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
        cnt = G.nbar_width - 1;
        do {
-               *ptr1++ = thispix;
-               *ptr2++ = thispix;
+               fb_write_pixel(ptr1, thispix);
+               fb_write_pixel(ptr2, thispix);
+               ptr1 += G.bytes_per_pixel;
+               ptr2 += G.bytes_per_pixel;
        } while (--cnt >= 0);
 
        // vertical lines
-       ptr1 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx) * BYTES_PER_PIXEL);
-       ptr2 = (DATA*)(G.addr + (G.nbar_posy * G.scr_var.xres + G.nbar_posx + G.nbar_width - 1) * BYTES_PER_PIXEL);
-       cnt = G.nbar_posy + G.nbar_height - 1 - G.nbar_posy;
+       ptr1 = G.addr + G.nbar_posy * G.scr_fix.line_length + G.nbar_posx * G.bytes_per_pixel;
+       ptr2 = G.addr + G.nbar_posy * G.scr_fix.line_length + (G.nbar_posx + G.nbar_width - 1) * G.bytes_per_pixel;
+       cnt = G.nbar_height - 1;
        do {
-               *ptr1 = thispix; ptr1 += G.scr_var.xres;
-               *ptr2 = thispix; ptr2 += G.scr_var.xres;
+               fb_write_pixel(ptr1, thispix);
+               fb_write_pixel(ptr2, thispix);
+               ptr1 += G.scr_fix.line_length;
+               ptr2 += G.scr_fix.line_length;
        } while (--cnt >= 0);
 }
 
 
 /**
- *     Draw filled rectangle on framebuffer
+ * Draw filled rectangle on framebuffer
  * \param nx1pos,ny1pos upper left position
  * \param nx2pos,ny2pos down right position
  * \param nred,ngreen,nblue rgb color
@@ -140,21 +263,19 @@ static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
        unsigned char nred, unsigned char ngreen, unsigned char nblue)
 {
        int cnt1, cnt2, nypos;
-       DATA thispix;
-       DATA *ptr;
+       unsigned thispix;
+       unsigned char *ptr;
 
-       nred   >>= 3;  // 5-bit red
-       ngreen >>= 2;  // 6-bit green
-       nblue  >>= 3;  // 5-bit blue
-       thispix = nblue + (ngreen << 5) + (nred << (5+6));
+       thispix = fb_pixel_value(nred, ngreen, nblue);
 
        cnt1 = ny2pos - ny1pos;
        nypos = ny1pos;
        do {
-               ptr = (DATA*)(G.addr + (nypos * G.scr_var.xres + nx1pos) * BYTES_PER_PIXEL);
+               ptr = G.addr + nypos * G.scr_fix.line_length + nx1pos * G.bytes_per_pixel;
                cnt2 = nx2pos - nx1pos;
                do {
-                       *ptr++ = thispix;
+                       fb_write_pixel(ptr, thispix);
+                       ptr += G.bytes_per_pixel;
                } while (--cnt2 >= 0);
 
                nypos++;
@@ -163,19 +284,20 @@ static void fb_drawfullrectangle(int nx1pos, int ny1pos, int nx2pos, int ny2pos,
 
 
 /**
- *     Draw a progress bar on framebuffer
+ * Draw a progress bar on framebuffer
  * \param percent percentage of loading
  */
 static void fb_drawprogressbar(unsigned percent)
 {
-       int i, left_x, top_y, width, height;
+       int left_x, top_y, pos_x;
+       unsigned width, height;
 
        // outer box
        left_x = G.nbar_posx;
        top_y = G.nbar_posy;
        width = G.nbar_width - 1;
        height = G.nbar_height - 1;
-       if ((height | width) < 0)
+       if ((int)(height | width) < 0)
                return;
        // NB: "width" of 1 actually makes rect with width of 2!
        fb_drawrectangle();
@@ -185,87 +307,111 @@ static void fb_drawprogressbar(unsigned percent)
        top_y++;
        width -= 2;
        height -= 2;
-       if ((height | width) < 0)
+       if ((int)(height | width) < 0)
                return;
-       fb_drawfullrectangle(
-                       left_x, top_y,
-                                       left_x + width, top_y + height,
-                       G.nbar_colr, G.nbar_colg, G.nbar_colb);
 
+       pos_x = left_x;
        if (percent > 0) {
+               int i, y;
+
                // actual progress bar
-               width = width * percent / 100;
+               pos_x += (unsigned)(width * percent) / 100;
+
+               y = top_y;
                i = height;
                if (height == 0)
                        height++; // divide by 0 is bad
                while (i >= 0) {
                        // draw one-line thick "rectangle"
                        // top line will have gray lvl 200, bottom one 100
-                       unsigned gray_level = 100 + i*100/height;
+                       unsigned gray_level = 100 + (unsigned)i*100 / height;
                        fb_drawfullrectangle(
-                                       left_x, top_y, left_x + width, top_y,
+                                       left_x, y, pos_x, y,
                                        gray_level, gray_level, gray_level);
-                       top_y++;
+                       y++;
                        i--;
                }
        }
+
+       fb_drawfullrectangle(
+                       pos_x, top_y,
+                       left_x + width, top_y + height,
+                       G.nbar_colr, G.nbar_colg, G.nbar_colb);
 }
 
 
 /**
- *     Draw image from PPM file
+ * Draw image from PPM file
  */
 static void fb_drawimage(void)
 {
-       char head[256];
-       char s[80];
        FILE *theme_file;
+       char *read_ptr;
        unsigned char *pixline;
        unsigned i, j, width, height, line_size;
 
-       memset(head, 0, sizeof(head));
-       theme_file = xfopen_stdin(G.image_filename);
+       if (LONE_DASH(G.image_filename)) {
+               theme_file = stdin;
+       } else {
+               int fd = open_zipped(G.image_filename);
+               if (fd < 0)
+                       bb_simple_perror_msg_and_die(G.image_filename);
+               theme_file = xfdopen_for_read(fd);
+       }
 
-       // parse ppm header
+       /* Parse ppm header:
+        * - Magic: two characters "P6".
+        * - Whitespace (blanks, TABs, CRs, LFs).
+        * - A width, formatted as ASCII characters in decimal.
+        * - Whitespace.
+        * - A height, ASCII decimal.
+        * - Whitespace.
+        * - The maximum color value, ASCII decimal, in 0..65535
+        * - Newline or other single whitespace character.
+        *   (we support newline only)
+        * - A raster of Width * Height pixels in triplets of rgb
+        *   in pure binary by 1 or 2 bytes. (we support only 1 byte)
+        */
+#define concat_buf bb_common_bufsiz1
+       read_ptr = concat_buf;
        while (1) {
-               if (fgets(s, sizeof(s), theme_file) == NULL)
+               int w, h, max_color_val;
+               int rem = concat_buf + sizeof(concat_buf) - read_ptr;
+               if (rem < 2
+                || fgets(read_ptr, rem, theme_file) == NULL
+               ) {
                        bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
-
-               if (s[0] == '#')
-                       continue;
-
-               if (strlen(head) + strlen(s) >= sizeof(head))
-                       bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
-
-               strcat(head, s);
-               if (head[0] != 'P' || head[1] != '6')
-                       bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
-
-               // width, height, max_color_val
-               if (sscanf(head, "P6 %u %u %u", &width, &height, &i) == 3)
+               }
+               read_ptr = strchrnul(read_ptr, '#');
+               *read_ptr = '\0'; /* ignore #comments */
+               if (sscanf(concat_buf, "P6 %u %u %u", &w, &h, &max_color_val) == 3
+                && max_color_val <= 255
+               ) {
+                       width = w; /* w is on stack, width may be in register */
+                       height = h;
                        break;
-// TODO: i must be <= 255!
+               }
        }
 
        line_size = width*3;
+       pixline = xmalloc(line_size);
+
        if (width > G.scr_var.xres)
                width = G.scr_var.xres;
        if (height > G.scr_var.yres)
                height = G.scr_var.yres;
-
-       pixline = xmalloc(line_size);
        for (j = 0; j < height; j++) {
-               unsigned char *pixel = pixline;
-               DATA *src = (DATA *)(G.addr + j * G.scr_fix.line_length);
+               unsigned char *pixel;
+               unsigned char *src;
 
                if (fread(pixline, 1, line_size, theme_file) != line_size)
                        bb_error_msg_and_die("bad PPM file '%s'", G.image_filename);
+               pixel = pixline;
+               src = G.addr + j * G.scr_fix.line_length;
                for (i = 0; i < width; i++) {
-                       unsigned thispix;
-                       thispix = (((unsigned)pixel[0] << 8) & 0xf800)
-                               | (((unsigned)pixel[1] << 3) & 0x07e0)
-                               | (((unsigned)pixel[2] >> 3));
-                       *src++ = thispix;
+                       unsigned thispix = fb_pixel_value(pixel[0], pixel[1], pixel[2]);
+                       fb_write_pixel(src, thispix);
+                       src += G.bytes_per_pixel;
                        pixel += 3;
                }
        }
@@ -275,12 +421,12 @@ static void fb_drawimage(void)
 
 
 /**
- *     Parse configuration file
+ * Parse configuration file
  * \param *cfg_filename name of the configuration file
  */
 static void init(const char *cfg_filename)
 {
-       static const char const param_names[] ALIGN1 =
+       static const char param_names[] ALIGN1 =
                "BAR_WIDTH\0" "BAR_HEIGHT\0"
                "BAR_LEFT\0" "BAR_TOP\0"
                "BAR_R\0" "BAR_G\0" "BAR_B\0"
@@ -291,11 +437,11 @@ static void init(const char *cfg_filename)
        char *token[2];
        parser_t *parser = config_open2(cfg_filename, xfopen_stdin);
        while (config_read(parser, token, 2, 2, "#=",
-                                   (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
-               unsigned val = xatoi_u(token[1]);
+                               (PARSE_NORMAL | PARSE_MIN_DIE) & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
+               unsigned val = xatoi_positive(token[1]);
                int i = index_in_strings(param_names, token[0]);
                if (i < 0)
-                       bb_error_msg_and_die("syntax error: '%s'", token[0]);
+                       bb_error_msg_and_die("syntax error: %s", token[0]);
                if (i >= 0 && i < 7)
                        G.ns[i] = val;
 #if DEBUG
@@ -340,7 +486,7 @@ int fbsplash_main(int argc UNUSED_PARAM, char **argv)
 
        if (fifo_filename && bCursorOff) {
                // hide cursor (BEFORE any fb ops)
-               full_write(STDOUT_FILENO, "\x1b" "[?25l", 6);
+               full_write(STDOUT_FILENO, "\033[?25l", 6);
        }
 
        fb_drawimage();
@@ -377,9 +523,7 @@ int fbsplash_main(int argc UNUSED_PARAM, char **argv)
                num = atoi(num_buf);
                if (isdigit(num_buf[0]) && (num <= 100)) {
 #if DEBUG
-                       char strVal[10];
-                       sprintf(strVal, "%d", num);
-                       DEBUG_MESSAGE(strVal);
+                       DEBUG_MESSAGE(itoa(num));
 #endif
                        fb_drawprogressbar(num);
                }
@@ -387,7 +531,7 @@ int fbsplash_main(int argc UNUSED_PARAM, char **argv)
        }
 
        if (bCursorOff) // restore cursor
-               full_write(STDOUT_FILENO, "\x1b" "[?25h", 6);
+               full_write(STDOUT_FILENO, "\033[?25h", 6);
 
        return EXIT_SUCCESS;
 }