/*
* image viewer, for framebuffer devices
*
* (c) 1998-2002 Gerd Knorr <
[email protected]>
*
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <getopt.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#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 <number>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,"<number>G: jump to image <number>");
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;
}
}
}