/* * image viewer, for framebuffer devices * * (c) 1998-2002 Gerd Knorr * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "loader.h" #include "dither.h" #include "fbtools.h" #include "fs.h" #include "jpeglib.h" #define TRUE 1 #define FALSE 0 #define MAX(x,y) ((x)>(y)?(x):(y)) #define MIN(x,y) ((x)<(y)?(x):(y)) #define KEY_EOF -1 /* ^D */ #define KEY_ESC -2 #define KEY_SPACE -3 #define KEY_Q -4 #define KEY_PGUP -5 #define KEY_PGDN -6 #define KEY_TIMEOUT -7 #define KEY_TAGFILE -8 #define KEY_PLUS -9 #define KEY_MINUS -10 #define DEFAULT_DEVICE "/dev/fb0" /* ---------------------------------------------------------------------- */ /* variables for read_image */ unsigned long lut_red[256], lut_green[256], lut_blue[256]; int dither = FALSE, pcd_res = 3, steps = 50; int textreading = 0, visible = 1, redraw = 0; int new_image, left, top; /* file list */ char **files; int fileanz, filenr; char *fbdev = NULL; char *mode = NULL; int fd, switch_last, debug; unsigned short red[256], green[256], blue[256]; struct fb_cmap cmap = { 0, 256, red, green, blue }; static float fbgamma = 1; /* Command line options. */ struct option fbi_options[] = { {"version", no_argument, NULL, 'v'}, /* version */ {"help", no_argument, NULL, 'h'}, /* help */ {"device", required_argument, NULL, 'd'}, /* device */ {"mode", required_argument, NULL, 'm'}, /* video mode */ {"gamma", required_argument, NULL, 'g'}, /* set gamma */ {"quiet", no_argument, NULL, 'q'}, /* quiet */ {"scroll", required_argument, NULL, 's'}, /* set scrool */ {"timeout", required_argument, NULL, 't'}, /* timeout value */ {"resolution", required_argument, NULL, 'r'}, /* select resolution */ {"random", no_argument, NULL, 'u'}, /* randomize images */ {"font", required_argument, NULL, 'f'}, /* font */ {"autozoom", no_argument, NULL, 'a'}, {0,0,0,0} }; /* font handling */ struct fs_font *f; char *x11_font = "10x20"; char *fontname = NULL; /* ---------------------------------------------------------------------- */ static void version(void) { fprintf(stderr, "fbi version " VERSION " (c) 1999-2001 Gerd Knorr; compiled on %s.\n", __DATE__ ); } static void usage(char *name) { char *h; if (NULL != (h = strrchr(name, '/'))) name = h+1; fprintf(stderr, "\n" "This program displays images using the Linux framebuffer device.\n" "Supported formats: PhotoCD, jpeg, ppm, gif, tiff, xwd, bmp, png.\n" "It tries to use ImageMagick's convert for unknown file formats.\n" "\n" " Usage: %s [ options ] file1 file2 ... fileN\n" "\n" " --help [-h] Print this text\n" " --version [-v] Show the fbi version number\n" " --device [-d] dev Framebuffer device [%s]\n" " --mode [-m] mode Video mode (must be listed in /etc/fb.modes)\n" " - Default is current mode.\n" " --gamma [-g] f Set gamma\n" " --scroll [-s] n Set scroll steps in pixels (default: 50)\n" " --quiet [-q] Be quiet: don't print anything at all\n" " --timeout [-t] n Load next image after N sec without any keypress\n" " --resolution [-r] n Select resolution [1..5] (PhotoCD)\n" " --random [-u] Show file1 .. fileN in a random order\n" " --font [-f] fn Use font fn (either console psf file or\n" " X11 font spec if a font server is available\n" " --autozoom [-a] Automagically pick useful zoom factor.\n" "\n" "Large images can be scrolled using the cursor keys. Zoom in/out\n" "works with '+' and '-'. Use ESC or 'q' to quit. Space and PgDn\n" "show the next, PgUp shows the previous image. Jumping to a image\n" "works with g. Return acts like Space but additionally\n" "prints the filename of the currently displayed image to stdout.\n" "when using the slideshow mode, '--timeout' [-t], pressing 'p' will\n" "pause on the current image, and '--random' [-u] will randomize the\n" "order of the images.\n" "\n", name, fbdev ? fbdev : "/dev/fb0"); } /* ---------------------------------------------------------------------- */ static void text_init(char *font) { char *fonts[2] = { font, NULL }; if (NULL == f) f = fs_consolefont(font ? fonts : NULL); #ifndef X_DISPLAY_MISSING if (NULL == f && 0 == fs_connect(NULL)) f = fs_open(font ? font : x11_font); #endif if (NULL == f) { fprintf(stderr,"no font available\n"); exit(1); } } static void text_out(int x, int y, char *str) { y *= f->height; y -= f->fontHeader.max_bounds.descent; x *= f->width; fs_puts(f,x,y,str); } /* ---------------------------------------------------------------------- */ struct termios saved_attributes; int saved_fl; static void tty_raw(void) { struct termios tattr; fcntl(0,F_GETFL,&saved_fl); tcgetattr (0, &saved_attributes); fcntl(0,F_SETFL,O_NONBLOCK); memcpy(&tattr,&saved_attributes,sizeof(struct termios)); tattr.c_lflag &= ~(ICANON|ECHO); tattr.c_cc[VMIN] = 1; tattr.c_cc[VTIME] = 0; tcsetattr (0, TCSAFLUSH, &tattr); } static void tty_restore(void) { fcntl(0,F_SETFL,saved_fl); tcsetattr (0, TCSANOW, &saved_attributes); } static void console_switch(int is_busy) { switch (fb_switch_state) { case FB_REL_REQ: fb_switch_release(); case FB_INACTIVE: visible = 0; break; case FB_ACQ_REQ: fb_switch_acquire(); case FB_ACTIVE: fb_memset(fb_mem,0,fb_fix.smem_len); ioctl(fd,FBIOPAN_DISPLAY,&fb_var); if (is_busy) text_out(0,0,"still busy, please wait ..."); visible = 1; redraw = 1; break; default: break; } switch_last = fb_switch_state; return; } /* ---------------------------------------------------------------------- */ static char * read_image(char *filename, int *gray, int *width, int *height) { char command[1024]; static char *image = NULL; struct ida_loader *loader; struct ida_image_info info; char blk[512]; FILE *fp; void *data; int l,y; if (image) { free(image); image = NULL; } new_image = 1; /* open file */ if (NULL == (fp = fopen(filename, "r"))) { fprintf(stderr,"open %s: %s\n",filename,strerror(errno)); return NULL; } memset(blk,0,sizeof(blk)); fread(blk,1,sizeof(blk),fp); rewind(fp); /* pick loader */ for (l = 0;; l++) { loader = loaders[l]; if (NULL == loader) { /* no loader found, try to use ImageMagick's convert */ sprintf(command,"convert \"%s\" ppm:-",filename); if (NULL == (fp = popen(command,"r"))) return NULL; loader = &ppm_loader; break; } if (NULL == loader->magic) break; if (0 == memcmp(blk+loader->moff,loader->magic,loader->mlen)) break; } /* load image */ data = loader->init(fp,filename,&info); if (NULL == data) { fprintf(stderr,"loading %s [%s] FAILED\n",filename,loader->name); return NULL; } *width = info.width; *height = info.height; *gray = 0; image = malloc((*width) * (*height) * 3); for (y = 0; y < (*height); y++) { if (switch_last != fb_switch_state) console_switch(1); loader->read(image + (*width)*3*y, y, data); } loader->done(data); return image; } /* ---------------------------------------------------------------------- */ static unsigned char * convert_line(int gray, int bpp, int line, int zoom, int owidth, char unsigned *dest, char unsigned *buffer) { unsigned char *ptr = (void*)dest; unsigned short *ptr2 = (void*)dest; unsigned long *ptr4 = (void*)dest; unsigned char *b; int in,out; int shift = zoom + 16; if (gray) { /* grayscale */ switch (bpp) { case 8: b = malloc(3*owidth); for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; b[out*3] = buffer[in]; b[out*3+1] = buffer[in]; b[out*3+2] = buffer[in]; } dither_line(b, ptr, line, owidth); free(b); ptr += owidth; return ptr; case 15: case 16: for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; ptr2[out] = lut_red[buffer[in]] | lut_green[buffer[in]] | lut_blue[buffer[in]]; } ptr2 += owidth; return (char*)ptr2; break; case 24: for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; ptr[3*out+2] = buffer[in]; ptr[3*out+1] = buffer[in]; ptr[3*out+0] = buffer[in]; } ptr += owidth * 3; return ptr; case 32: for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; ptr4[out] = lut_red[buffer[in]] | lut_green[buffer[in]] | lut_blue[buffer[in]]; } ptr4 += owidth; return (char*)ptr4; } } else { /* colors */ switch (fb_var.bits_per_pixel) { case 8: b = malloc(3*owidth); for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; b[out*3] = buffer[in*3]; b[out*3+1] = buffer[in*3+1]; b[out*3+2] = buffer[in*3+2]; } dither_line(b, ptr, line, owidth); free(b); ptr += owidth; return ptr; case 15: case 16: for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; ptr2[out] = lut_red[buffer[in*3]] | lut_green[buffer[in*3+1]] | lut_blue[buffer[in*3+2]]; } ptr2 += owidth; return (char*)ptr2; case 24: for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; ptr[3*out+2] = buffer[3*in+0]; ptr[3*out+1] = buffer[3*in+1]; ptr[3*out+0] = buffer[3*in+2]; } ptr += owidth * 3; return ptr; case 32: for (out = 0; out < owidth; out++) { in = (out << 16) >> shift; ptr4[out] = lut_red[buffer[in*3]] | lut_green[buffer[in*3+1]] | lut_blue[buffer[in*3+2]]; } ptr4 += owidth; return (char*)ptr4; } } /* keep compiler happy */ return NULL; } static unsigned char * transform_image(unsigned char *iimage, int iwidth, int iheight, int gray, int zoom, int *owidth, int *oheight) { int in,out,inlength,outlength; unsigned char *ptr; static char *image = NULL; int shift = zoom + 16; if (image) { free(image); image = NULL; } *owidth = iwidth; *oheight = iheight; if (zoom < 0) { *owidth >>= -zoom; *oheight >>= -zoom; } else { *owidth <<= zoom; *oheight <<= zoom; } inlength = iwidth * (gray ? 1 : 3); outlength = (*owidth) * ((fb_var.bits_per_pixel+7)/8); image = ptr = malloc(outlength * (*oheight)); if (NULL != image) { for (out = 0; out < *oheight; out++) { if (switch_last != fb_switch_state) console_switch(1); in = (out << 16) >> shift; ptr = convert_line(gray, fb_var.bits_per_pixel, out, zoom, *owidth, ptr, iimage + (in*inlength)); } } return image; } /* ---------------------------------------------------------------------- */ static void lut_init(int depth) { register int i; /* init Lookup tables */ switch (depth) { case 15: for (i = 0; i < 256; i++) { lut_red[i] = (i & 0xf8) << 7; /* bits -rrrrr-- -------- */ lut_green[i] = (i & 0xf8) << 2; /* bits------gg ggg----- */ lut_blue[i] = (i & 0xf8) >> 3; /* bits-------- ---bbbbb */ } break; case 16: for (i = 0; i < 256; i++) { lut_red[i] = (i & 0xf8) << 8; /* bits rrrrr--- -------- */ lut_green[i] = (i & 0xfc) << 3; /* bits -----ggg ggg----- */ lut_blue[i] = (i & 0xf8) >> 3; /* bits -------- ---bbbbb */ } break; case 24: for (i = 0; i < 256; i++) { lut_red[i] = i << 16; /* byte -r-- */ lut_green[i] = i << 8; /* byte --g- */ lut_blue[i] = i; /* byte ---b */ } break; } } static unsigned short calc_gamma(int n, int max) { int ret =65535.0 * pow((float)n/(max), 1 / fbgamma); if (ret > 65535) ret = 65535; if (ret < 0) ret = 0; return ret; } static void linear_palette(int bit) { int i, size = 256 >> (8 - bit); for (i = 0; i < size; i++) red[i] = green[i] = blue[i] = calc_gamma(i,size); } static void svga_dither_palette(int r, int g, int b) { int rs, gs, bs, i; rs = 256 / (r - 1); gs = 256 / (g - 1); bs = 256 / (b - 1); for (i = 0; i < 256; i++) { red[i] = calc_gamma(rs * ((i / (g * b)) % r), 255); green[i] = calc_gamma(gs * ((i / b) % g), 255); blue[i] = calc_gamma(bs * ((i) % b), 255); } } static void svga_display_image(char *image, int width, int height, int xoff, int yoff) { int dwidth = MIN(width, fb_var.xres); int dheight = MIN(height, fb_var.yres); int data, video, bank, offset, bytes; if (!visible) return; bytes = (fb_var.bits_per_pixel+7)/8; /* offset for image data (image > screen, select visible area) */ offset = (yoff * width + xoff) * bytes; /* offset for video memory (image < screen, center image) */ video = 0, bank = 0; if (width < fb_var.xres) video += bytes * ((fb_var.xres - width) / 2); if (height < fb_var.yres) video += fb_fix.line_length * ((fb_var.yres - height) / 2); /* go ! */ for (data = 0; data < width * height * bytes && data / width / bytes < dheight; data += width * bytes, video += fb_fix.line_length) { memcpy(fb_mem+video, &image[data + offset], dwidth * bytes); } } static int svga_show(char *image, int width, int height, int timeout) { static int paused = 0; int rc; char key[11]; int nr = 0; fd_set set; struct timeval limit; if (NULL == image) return KEY_SPACE; /* skip */ if (new_image) { /* start with centered image, if larger than screen */ if (width > fb_var.xres) left = (width - fb_var.xres) / 2; if (height > fb_var.yres && !textreading) top = (height - fb_var.yres) / 2; new_image = 0; } redraw = 1; for (;;) { if (redraw) { redraw = 0; if (height <= fb_var.yres) { top = 0; } else { if (top < 0) top = 0; if (top + fb_var.yres > height) top = height - fb_var.yres; } if (width <= fb_var.xres) { left = 0; } else { if (left < 0) left = 0; if (left + fb_var.xres > width) left = width - fb_var.xres; } svga_display_image(image, width, height, left, top); } if (switch_last != fb_switch_state) { console_switch(0); continue; } FD_SET(0, &set); limit.tv_sec = timeout; limit.tv_usec = 0; rc = select(1, &set, NULL, NULL, (-1 != timeout && !paused) ? &limit : NULL); if (switch_last != fb_switch_state) { console_switch(0); continue; } if (0 == rc) return KEY_TIMEOUT; rc = read(0, key, 10); if (rc < 1) { /* EOF */ return KEY_EOF; } key[rc] = 0; if (rc == 1 && (*key == 'g' || *key == 'G')) { return nr; } if (rc == 1 && *key >= '0' && *key <= '9') { nr = nr * 10 + (*key - '0'); } else { nr = 0; } if (rc == 1 && (*key == 'q' || *key == 'Q' || *key == 'e' || *key == 'E' || *key == '\x1b' || *key == '\n')) { if (*key == '\n') return KEY_TAGFILE; if (*key == '\x1b') return KEY_ESC; return KEY_Q; } else if (0 == strcmp(key, " ")) { if (textreading && top < height - fb_var.yres) { redraw = 1; top += (fb_var.yres-100); } else { return KEY_SPACE; } } else if (0 == strcmp(key, "\x1b[A") && height > fb_var.yres) { redraw = 1; top -= steps; } else if (0 == strcmp(key, "\x1b[B") && height > fb_var.yres) { redraw = 1; top += steps; } else if (0 == strcmp(key, "\x1b[1~") && height > fb_var.yres) { redraw = 1; top = 0; } else if (0 == strcmp(key, "\x1b[4~")) { redraw = 1; top = height - fb_var.yres; } else if (0 == strcmp(key, "\x1b[D") && width > fb_var.xres) { redraw = 1; left -= steps; } else if (0 == strcmp(key, "\x1b[C") && width > fb_var.xres) { redraw = 1; left += steps; } else if (0 == strcmp(key, "\x1b[5~")) { return KEY_PGUP; } else if (0 == strcmp(key, "\x1b[6~")) { return KEY_PGDN; } else if (0 == strcmp(key, "+")) { return KEY_PLUS; } else if (0 == strcmp(key, "-")) { return KEY_MINUS; } else if (0 == strcmp(key, "p") || 0 == strcmp(key, "P")) { if (-1 != timeout) { paused = !paused; text_out(0,0, paused ? "pause on " : "pause off"); } #if 0 } else { /* testing: find key codes */ int i,len; char linebuffer[80]; len = sprintf(linebuffer,"debug: key: "); for (i = 0; i < rc; i++) len += sprintf(linebuffer+len, "%s%c", key[i] < 0x20 ? "^" : "", key[i] < 0x20 ? key[i] + 0x40 : key[i]); text_out(0,0,linebuffer); #endif } } } /* ---------------------------------------------------------------------- */ int main(int argc, char *argv[]) { int timeout = -1, verbose = 1; int randomize = -1; int opt_index = 0; int igray,iwidth,iheight; int width, height; unsigned char *iimage = NULL,*image = NULL; int c, rc, zoom = 0, autozoom = 0; int need_read, need_refresh; int rand_one, rand_two; char *rand_temp; int i; char *basename, *line; char linebuffer[128]; if (NULL != (line = getenv("FRAMEBUFFER"))) fbdev = line; if (NULL != (line = getenv("FBGAMMA"))) fbgamma = atof(line); if (NULL != (line = getenv("FBFONT"))) fontname = line; for (;;) { c = getopt_long(argc, argv, "uvahpqr:t:m:d:g:s:f:", fbi_options, &opt_index); if (c == -1) break; switch (c) { case 'a': autozoom = 1; break; case 'q': verbose = 0; break; case 'p': textreading = 1; break; case 'g': fbgamma = atof(optarg); break; case 'r': pcd_res = atoi(optarg); break; case 's': steps = atoi(optarg); break; case 't': timeout = atoi(optarg); break; case 'u': randomize = 1; break; case 'd': fbdev = optarg; break; case 'm': mode = optarg; break; case 'f': fontname = optarg; break; case 'v': version(); exit(1); break; default: case 'h': usage(argv[0]); exit(1); } } if (optind == argc) { usage(argv[0]); exit(1); } files = argv + optind; fileanz = argc - optind; filenr = 0; /* Seed random number gen. (sic) */ srand((unsigned)time(NULL ) ); /* Randomize the order of the images */ if (randomize != -1) { /* Naive implementation - could be improved. */ for( i = 0; i < fileanz; i++ ) { rand_one = rand() % fileanz; rand_two = rand() % fileanz; rand_temp = files[ rand_one ]; files[ rand_one ] = files[ rand_two ]; files[ rand_two ] = rand_temp; } } need_read = 1; need_refresh = 1; text_init(fontname); fd = fb_init(fbdev, mode, 0); fb_catch_exit_signals(); fb_switch_init(); signal(SIGTSTP,SIG_IGN); fs_init_fb(255); switch (fb_var.bits_per_pixel) { case 8: svga_dither_palette(8, 8, 4); dither = TRUE; init_dither(8, 8, 4, 2); break; case 15: case 16: if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR) linear_palette(5); if (fb_var.green.length == 5) { lut_init(15); } else { lut_init(16); } break; case 24: if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR) linear_palette(8); break; case 32: if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR) linear_palette(8); lut_init(24); break; default: fprintf(stderr, "Oops: %i bit/pixel ???\n", fb_var.bits_per_pixel); exit(1); } if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR || fb_var.bits_per_pixel == 8) { if (-1 == ioctl(fd,FBIOPUTCMAP,&cmap)) { perror("ioctl FBIOPUTCMAP"); exit(1); } } if (verbose) { sprintf(linebuffer,"video mode: %ix%i", fb_var.xres, fb_var.yres); text_out(0,2,linebuffer); text_out(0,3,"ESC,Q: quit"); text_out(0,4,"PgDn,space: next image"); text_out(0,4,"Return: print current file to stdout, display next"); text_out(0,5,"PgUp: previous image"); text_out(0,6,"G: jump to image "); text_out(0,7,"cursor keys: scroll large images"); text_out(0,8,"P: pause slideshow (if started with -t)"); } /* svga main loop */ tty_raw(); for (;;) { if (need_read) { need_read = 0; need_refresh = 1; if (verbose && visible) { basename = strrchr(files[filenr], '/'); basename = basename ? basename + 1 : files[filenr]; sprintf(linebuffer,"loading %s... ", basename); text_out(0,0,linebuffer); } iimage = read_image(files[filenr], &igray, &iwidth, &iheight); if (autozoom) { width = iwidth; height = iheight; zoom = 0; while (width*2 < fb_var.xres && height*2 < fb_var.yres) width <<= 1, height <<= 1, zoom++; while (width > fb_var.xres || height > fb_var.yres) width >>= 1, height >>= 1, zoom--; } if (iimage) { if (verbose && visible) { sprintf(linebuffer+strlen(linebuffer), "scaling (%d)... ",zoom); text_out(0,0,linebuffer); } image = transform_image(iimage,iwidth,iheight,igray,zoom, &width,&height); } else { image = NULL; } if (NULL == image && verbose && visible) { strcat(linebuffer,"FAILED "); text_out(0,0,linebuffer); sleep(1); } } if (need_refresh) { need_refresh = 0; if (width < fb_var.xres || height < fb_var.yres) if (visible) fb_memset(fb_mem,0,fb_fix.smem_len); } switch (rc = svga_show(image, width, height, timeout)) { case KEY_TAGFILE: printf("%s\n",files[filenr]); /* fall throuth */ case KEY_SPACE: if (filenr != fileanz - 1) { filenr++, need_read = 1; break; } /* else fall */ case KEY_ESC: case KEY_Q: case KEY_EOF: if (visible) fb_memset(fb_mem,0,fb_fix.smem_len); tty_restore(); fb_cleanup(); exit(0); break; case KEY_PGDN: if (filenr < fileanz - 1) filenr++, need_read = 1; break; case KEY_PGUP: if (filenr > 0) filenr--, need_read = 1; break; case KEY_TIMEOUT: need_read = 1; filenr++; if (filenr == fileanz) filenr = 0; break; case KEY_PLUS: case KEY_MINUS: if (rc == KEY_PLUS && zoom < 8) { zoom++; top = top * 2 + fb_var.yres/2; left = left * 2 + fb_var.xres/2; if (height < fb_var.yres) top -= fb_var.yres-height; if (width < fb_var.xres) left -= fb_var.xres-width; } if (rc == KEY_MINUS && zoom > -8) { zoom--; top = top / 2 - fb_var.yres/4; left = left / 2 - fb_var.xres/4; } if (verbose && visible) { sprintf(linebuffer,"scaling (%d)... ",zoom); text_out(0,0,linebuffer); } image = transform_image(iimage,iwidth,iheight,igray,zoom, &width,&height); need_refresh = 1; break; default: if (rc > 0 && rc <= fileanz) filenr = rc - 1, need_read = 1; break; } } } .