/*
 * display.c
 *
 * Copyright (C) 1993-1999 Evan Harris
 *
 * Permission is granted to freely redistribute and modify this code,
 * providing the author(s) get credit for having written it.
 *
 */

/* 	$Id: display.c,v 1.10 1999/11/07 05:54:35 evan Exp $	 */

#ifndef lint
static char vcid[] = "$Id: display.c,v 1.10 1999/11/07 05:54:35 evan Exp $";
#endif /* lint */

/*
 * (Evan) PIX_ALIGN changes inspired by Michael Weller, who patched against
 * version 1.1:
 *
 * Patched to not depend on illegal assumptions (xbytes==bytesperpixel*xdim),
 *         to obey logical_width a multiple of 8 pixels (not bytes)
 *         to let logical_width be an even higher multiple of a power of two
 *         for spike free video signal on Mach32
 * 16. 3. 94 Michael Weller (eowmob@exp-math.uni-essen.de or
 * eowmob@pollux.exp-math.uni-essen.de or mat42b@vm.hrz.uni-essen.de or
 * mat42b@de0hrz1a.bitnet) (svgalib, mach32-driver)
 */

#include "seejpeg.h"
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <vga.h>



#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define ABS(a) (((a) < 0) ? -(a) : (a))


static int best_mode_256[] =
{
    G320x200x256,
#ifndef ONLY_1_8_MODES
    G320x240x256V,
    G400x300x256,
    G512x384x256,
    G640x400x256,
#endif
    G640x480x256,
#ifndef ONLY_1_8_MODES
    G720x540x256,
#endif
    G800x600x256,
#ifndef ONLY_1_8_MODES
    G848x480x256,
    G960x720x256,
#endif
    G1024x768x256,
#ifndef ONLY_1_8_MODES
    G1072x600x256,
#endif
    G1152x864x256,
#ifndef ONLY_1_8_MODES
    G1280x720x256,
#endif
    G1280x1024x256,
#ifndef ONLY_1_8_MODES
    G1360x768x256,
#endif
    G1600x1200x256,
#ifndef ONLY_1_8_MODES
    G1800x1012x256,
    G1920x1440x256,
    G2048x1152x256,
    G2048x1536x256,
#endif
    TEXT
};

static int best_mode_32k16m[] =
{
    G320x200x16M32,
    G320x200x16M,
    G320x200x32K,
#ifndef ONLY_1_8_MODES
    G320x240x16M32,
    G320x240x16M,
    G320x240x32K,
    G400x300x16M32,
    G400x300x16M,
    G400x300x32K,
    G512x384x16M32,
    G512x384x16M,
    G512x384x32K,
    G640x400x16M32,
    G640x400x16M,
    G640x400x32K,
#endif
    G640x480x16M32,
    G640x480x16M,
    G640x480x32K,
#ifndef ONLY_1_8_MODES
    G720x540x16M32,
    G720x540x16M,
    G720x540x32K,
#endif
    G800x600x16M32,
    G800x600x16M,
    G800x600x32K,
#ifndef ONLY_1_8_MODES
    G848x480x16M32,
    G848x480x16M,
    G848x480x32K,
    G960x720x16M32,
    G960x720x16M,
    G960x720x32K,
#endif
    G1024x768x16M32,
    G1024x768x16M,
    G1024x768x32K,
#ifndef ONLY_1_8_MODES
    G1072x600x16M32,
    G1072x600x16M,
    G1072x600x32K,
#endif
    G1152x864x16M32,
    G1152x864x16M,
    G1152x864x32K,
#ifndef ONLY_1_8_MODES
    G1280x720x16M32,
    G1280x720x16M,
    G1280x720x32K,
#endif
    G1280x1024x16M32,
    G1280x1024x16M,
    G1280x1024x32K,
#ifndef ONLY_1_8_MODES
    G1360x768x16M32,
    G1360x768x16M,
    G1360x768x32K,
#endif
    G1600x1200x16M32,
    G1600x1200x16M,
    G1600x1200x32K,
#ifndef ONLY_1_8_MODES
    G1800x1012x16M32,
    G1800x1012x16M,
    G1800x1012x32K,
    G1920x1440x16M32,
    G1920x1440x16M,
    G1920x1440x32K,
    G2048x1152x16M32,
    G2048x1152x16M,
    G2048x1152x32K,
    G2048x1536x16M32,
    G2048x1536x16M,
    G2048x1536x32K,
#endif
    TEXT
};


static int PIX_ALIGN;

static int logical_width, logical_height;
static int logical_byte_width, bytesperpixel;
static int mode_width, mode_height, mode_depth, start_row, mode_linewidth;


bool
is_supported_256_mode(int mode)
{
    int i = 0;
    do
    {
	if (best_mode_256[i] == mode)
	{
	    return TRUE;
	}
    }
    while (best_mode_256[++i] != TEXT);
    return FALSE;
}


bool
is_supported_32k16m_mode(int mode)
{
    int i = 0;
    do
    {
	if (best_mode_32k16m[i] == mode)
	{
	    return TRUE;
	}
    }
    while (best_mode_32k16m[++i] != TEXT);
    return FALSE;
}


/*
 * Finds the given mode in the table of modes, and sets the width, height and
 * depth parameters.
 *
 * Returns true if the mode was found, and false if it wasn't.
 */
static bool
find_mode_in_table(int mode,
		   int* table,
		   int* width,
		   int* height,
		   int* depth)
{
    vga_modeinfo* vgainfo;
    int i = 0;
    do
    {
	if (table[i] == mode)
	{
	    vgainfo = vga_getmodeinfo(mode);
	    *width = vgainfo->width;
	    *height = vgainfo->height;
	    *depth = vgainfo->colors;
	    return TRUE;
	}
    }
    while (table[++i] != TEXT);
    return FALSE;
}


static bool
mode_enabled(int width, int height)
{
    bool enabled;
    
    /* Mode is one of: 5x4, 4x3, 8x5, 16x9 */

    if (width * 4 == height * 5)
    {
	enabled = (opt_enable_5x4 ? TRUE : FALSE);
    }
    else if (width * 3 == height * 4)
    {
	enabled = (opt_enable_4x3 ? TRUE : FALSE);
    }
    else if (width * 5 == height * 8)
    {
	enabled = (opt_enable_8x5 ? TRUE : FALSE);
    }
    else if (ABS(width * 9 - height * 16) <= 48)
    {
	enabled = (opt_enable_16x9 ? TRUE : FALSE);
    }
    else
    {
	fprintf(stderr, "Unknown aspect ratio: %dx%d\n", width, height);
	enabled = FALSE;
    }

    return enabled;
}


static int
select_best_256_mode(int width, int height)
{
    vga_modeinfo* vgainfo;
    int mode = TEXT;
    int last_usable_mode = TEXT;
    int i = 0;

    do
    {
	if (vga_hasmode(best_mode_256[i]))
	{
	    vgainfo = vga_getmodeinfo(best_mode_256[i]);
	    if (mode_enabled(vgainfo->width, vgainfo->height))
	    {
		if (vgainfo->width >= opt_fuzz * width
		    && (opt_widthonly
			|| vgainfo->height >= opt_fuzz * height)
		    && (opt_minwidth == 0
			|| vgainfo->width >= opt_minwidth)
		    && (opt_maxwidth == 0
			|| vgainfo->width <= opt_maxwidth))
		{
		    mode = best_mode_256[i];
		    mode_width = vgainfo->width;
		    mode_height = vgainfo->height;
		    mode_depth = vgainfo->colors;
		}
		last_usable_mode = best_mode_256[i];
	    }
	}
    }
    while (mode == TEXT && best_mode_256[++i] != TEXT);

    if (mode == TEXT && last_usable_mode != TEXT)
    {
	vgainfo = vga_getmodeinfo(last_usable_mode);
	mode = last_usable_mode;
	mode_width = vgainfo->width;
	mode_height = vgainfo->height;
	mode_depth = vgainfo->colors;
    }

    return mode;
}


static int
select_best_colour_mode(int width, int height)
{
#ifndef NO_32K_CASCADE
    int bytesperpixel, logical_byte_width, logical_width, logical_height;
#endif  
    vga_modeinfo *vgainfo;
    int mode = TEXT;
    int last_usable_mode = TEXT;
    int i = 0;

    do
    {
	if (vga_hasmode(best_mode_32k16m[i]))
	{
	    vgainfo = vga_getmodeinfo(best_mode_32k16m[i]);
	    if (mode_enabled(vgainfo->width, vgainfo->height))
	    {
		if (vgainfo->width >= opt_fuzz * width
		    && (opt_widthonly
			|| vgainfo->height >= opt_fuzz * height)
		    && (opt_minwidth == 0
			|| vgainfo->width >= opt_minwidth)
		    && (opt_maxwidth == 0
			|| vgainfo->width <= opt_maxwidth))
		{
		    mode = best_mode_32k16m[i];
		    mode_width = vgainfo->width;
		    mode_height = vgainfo->height;
		    mode_depth = vgainfo->colors;

#ifndef NO_32K_CASCADE
		    bytesperpixel = vgainfo->bytesperpixel;
		    mode_linewidth = vgainfo->linewidth;
		
		    logical_byte_width =
			MAX(mode_linewidth, width * bytesperpixel);
		    if (logical_byte_width % PIX_ALIGN != 0)
		    {
			logical_byte_width +=
			    PIX_ALIGN - logical_byte_width % PIX_ALIGN;
		    }
		    if (logical_byte_width % (bytesperpixel * PIX_ALIGN) != 0)
		    {
			logical_byte_width +=
			    ((bytesperpixel * PIX_ALIGN)
			     - logical_byte_width % (bytesperpixel * PIX_ALIGN));
		    }
		    logical_width = logical_byte_width / bytesperpixel;
		    logical_height = MIN(vgainfo->maxpixels
					 * bytesperpixel / logical_byte_width,
					 height);
		    if (height > logical_height && mode_depth != 32768)
		    {
			mode = TEXT; /* assume a 32k mode will be available */
		    }
#endif
		}
		last_usable_mode = best_mode_32k16m[i];
	    }
	}
    }
    while (mode == TEXT && best_mode_32k16m[++i] != TEXT);

    if (mode == TEXT && last_usable_mode != TEXT)
    {
	vgainfo = vga_getmodeinfo(last_usable_mode);
	mode = last_usable_mode;
	mode_width = vgainfo->width;
	mode_height = vgainfo->height;
	mode_depth = vgainfo->colors;
    }

    return mode;
}


/*
 * Select the best video mode for the current video card.
 *
 * components: 3 for RGB, 1 for grayscale or colourmapped
 */

int
best_mode(int width, int height, int components)
{
    int mode = TEXT;

    if (opt_forcemode != TEXT)
    {
	if (vga_hasmode(opt_forcemode))
	{
	    if (components == 1)
	    {
		if (find_mode_in_table(opt_forcemode, &best_mode_256[0],
				       &mode_width, &mode_height, &mode_depth))
		{
		    return opt_forcemode;
		}
		else
		{
		    error_exit("Selected image requires a 256 colour mode");
		}
	    }
	    else if (components == 3)
	    {
		if (find_mode_in_table(opt_forcemode, &best_mode_32k16m[0],
				       &mode_width, &mode_height, &mode_depth))
		{
		    return opt_forcemode;
		}
		else
		{
		    error_exit("Selected image requires a 32K or 16M colour mode (otherwise use -q or -1 or -g)");
		}
	    }
	    else
	    {
		error_exit("Selected mode not supported for this image type");
	    }
	}
	else
	{
	    error_exit("Selected mode not supported by chipset and/or video card");
	}
    }

    if (components == 1)
    {
	mode = select_best_256_mode(width, height);
	/* selecting also sets mode_width, mode_height, mode_depth */
	if (mode == TEXT)
	{
	    error_exit("Cannot find a 256 colour video mode");
	}

    }
    else if (components == 3)
    {
	mode = select_best_colour_mode(width, height);
	/* selecting also sets mode_width, mode_height, mode_depth */
	if (mode == TEXT)
	{
	    error_exit("Cannot find a 32K/16M colour video mode");
	}

    }
    else
    {
	error_exit("Cannot cope with number of output components");
    }

    return mode;
}


void
clear_screen(int mode_width,
	     int mode_height,
	     int logical_width,
	     int logical_height)
{
    int line, lines;
  
    vga_screenoff();
  
    /* it's done by vga_setmode() but it can't hurt */
    vga_clear();			

    vga_setpalette(0, 0, 0, 0);
    vga_setcolor(0);
  
    logical_height = MAX(logical_height, mode_height);
  
    /* fillblit is much better, but it's not general */
    if ((logical_width * logical_height + mode_width - 1) / mode_width
	> mode_height)
    {
	lines = ((logical_width * logical_height + mode_width - 1)
		 / mode_width - mode_height);
	for (line = mode_height; line < mode_height + lines; line++)
	{
	    vga_drawline(0, line, mode_width, line);
	}
    }

    vga_screenon();
}


/* ----------------------------------------------------------------- */


/*
 * For gamma corrected output:
 *
 * output_val = val ^ 1/gamma
 *
 * where 0 <= val <= 1
 */

#define MAX_GAMMA_INDEX (GAMMA_TABLE_SIZE - 1)

unsigned int gamma_table[GAMMA_TABLE_SIZE];
unsigned int gamma_table_shift2[GAMMA_TABLE_SIZE];
unsigned int gamma_table_shift3[GAMMA_TABLE_SIZE];


void
gamma_table_init(double gamma)
{
    int i;
    double val, output_val;
    double inverse_gamma = 1.0 / gamma;
    
    for (i = 0; i < GAMMA_TABLE_SIZE; i++ )
    {
	/*
	 * type conversion and pow() is slow, but this is only called
         * during startup
	 */
	val = (double)i / (double)MAX_GAMMA_INDEX;
	output_val = pow(val, inverse_gamma);

	gamma_table[i] = (int)(0.5 + MAX_GAMMA_INDEX * output_val);
	gamma_table_shift2[i] = gamma_table[i] >> 2;
	gamma_table_shift3[i] = gamma_table[i] >> 3;
    }
}


/* ----------------------------------------------------------------- */


static int trident = 0;


void
chiptype_init(void)
{
    /*
     * Set PIX_ALIGN to be 64 because it seems to be a nice safe value.
     * Some cards can handle values much lower (which is desirable).
     * If your card can I would recommend trying values of 8, 16 or 32
     * before 64.
     */
    switch (vga_getcurrentchipset())
    {
	case TVGA8900:
	    PIX_ALIGN = 8;	/* 8 is safe */
	    trident = 1;	/* due to the 800x600x256 bug */
	    break;

	case CIRRUS:
	    PIX_ALIGN = 8;	/* the minimum is 8 */
	    break;

	case MACH32:
	    PIX_ALIGN = 64;	/* the minimum is 64 */
	    break;

	case NV3:
	    PIX_ALIGN = 8;	/* the minimum is 1 */
	    break;

	default:
	    PIX_ALIGN = 64;	/* a safe default */
	    break;
    }
}


/* ----------------------------------------------------------------- */


void
display_init(int image_width, int image_height, int components)
{
    int mode;			/* our video mode */
    int width, height;
    vga_modeinfo *vgainfo;

    width = (opt_doublex ? 2 * image_width : image_width);
    height = (opt_doubley ? 2 * image_height : image_height);

    mode = best_mode(width, height, components);

    vgainfo = vga_getmodeinfo(mode);

    bytesperpixel = vgainfo->bytesperpixel;
    mode_linewidth = vgainfo->linewidth;
  
    logical_byte_width = MAX(mode_linewidth, width * bytesperpixel);
    if (logical_byte_width % PIX_ALIGN != 0)
    {
	logical_byte_width += PIX_ALIGN - logical_byte_width % PIX_ALIGN;
    }

    /*
     * I don't really understand why we need this.
     * I wonder if it's documented somewhere...
     * 
     * (Michael, Mach32): well some cards want multiples of 8pixels in a row..
     * it is that easy.. 
     */
    if (logical_byte_width % (bytesperpixel * PIX_ALIGN) != 0)
    {
	logical_byte_width += (bytesperpixel * PIX_ALIGN)
	    - logical_byte_width % (bytesperpixel * PIX_ALIGN);
    }

    while (logical_byte_width / bytesperpixel
	   > vgainfo->maxpixels / mode_height)
    {
	logical_byte_width -= bytesperpixel * PIX_ALIGN;
    }

    logical_width = logical_byte_width / bytesperpixel;
    logical_height = MIN(vgainfo->maxpixels
			 * bytesperpixel / logical_byte_width, height);

    /*
     * There is a problem with Trident's 800x600 mode, it does not correctly
     * support logical video modes larger than then the visible video mode.
     */
    if (trident && mode == G800x600x256)
    {
	logical_width = 800;
	logical_byte_width = 800;
    }

    if (opt_verbose)
    {
	char s[20];
	int s_pos;
	sprintf(s, "%4dx%4dx", image_width, image_height);
	s_pos = strlen(s);
	if (components == 1)
	{
	    sprintf(&s[s_pos], "256");
	}
	else if (components == 3)
	{
	    sprintf(&s[s_pos], "16M");
	}
	printf("%-14s %-16s ", s, vga_getmodename(mode));
	printf("%4dx%4d\n", logical_width, logical_height);
    }

#ifdef BUG_WORKAROUND
    vga_setmode(TEXT);
#endif	
    if (vga_getcurrentmode() != mode)
    {
	vga_setmode(mode);
    }
    vga_setdisplaystart(0);
    if (!trident || mode != G800x600x256)
    {
	vga_setlogicalwidth(logical_byte_width);
    }

    start_row = 0;

    clear_screen(mode_width, mode_height, logical_width, logical_height);
}


void
display_set_palette(int num_colors, JSAMPARRAY colormap, int components)
{
    int i, r, g, b;

    for (i = 0; i < num_colors; i++)
    {
	/* we should put these in an array so there's only one syscall */
	if (components == 1)
	{
	    r = g = b = gamma_table_shift2[GETJSAMPLE(colormap[0][i])];
	}
	else if (components == 3)
	{
	    r = gamma_table_shift2[GETJSAMPLE(colormap[0][i])];
	    g = gamma_table_shift2[GETJSAMPLE(colormap[1][i])];
	    b = gamma_table_shift2[GETJSAMPLE(colormap[2][i])];
	}
	else
	{
	    error_exit("Cannot cope with number of colour components");
	    /* cannot reach here, but keep the compiler happy */
	    r = g = b = 0;
	}
	vga_setpalette(i, r, g, b);
    }
}


void
display_set_greyscale_palette(void)
{
    int i, c;

    for (i = 0; i < 256; i++)
    {
	c = gamma_table_shift2[i];
	vga_setpalette(i, c, c, c);
    }
}


void
display_rows(int num_rows,
	     JSAMPARRAY pixel_data,
	     int image_width,
	     int components)
{
    JSAMPROW ptr;
    int row, col;
    int width, height;
    int mode_row, mode_col;
    int centre_row, centre_col;
    int r, g, b;

    if (opt_doublex)
    {
	width = MIN(image_width, logical_width / 2);
    }
    else
    {
	width = MIN(image_width, logical_width);
    }

    if (start_row < logical_height)
    {
	if (logical_height < start_row + num_rows)
	{
	    height = logical_height - start_row;
	}
	else
	{
	    height = num_rows;
	}
    }
    else
    {
	start_row += num_rows;
	return;
    }
    
    if (opt_centre && logical_height < mode_height)
    {
	centre_row = (mode_height - logical_height) / 2;
    }
    else
    {
	centre_row = 0;
    }

    centre_col = 0;
    if (opt_centre)
    {
	int w = (opt_doublex ? image_width * 2 : image_width);
	if (w < mode_width)
	{
	    centre_col = (mode_width - w) / 2;
	}
    }
    
    if (components == 1)
    {
	for (row = 0; row < height; row++)
	{
	    int start_pos =
		(start_row + row + centre_row) * logical_byte_width;
	    mode_row = start_pos / mode_linewidth;
	    mode_col = ((start_pos % mode_linewidth) / bytesperpixel)
		+ centre_col;
	    ptr = pixel_data[row];
      
	    /*
	     * Duplicated code so we only do an unnecessary test per line
	     * rather than per pixel.
	     */
	    if (opt_doublex)
	    {
		for (col = 0; col < width; col++)
		{
		    vga_setcolor(GETJSAMPLE(*ptr++));
		    vga_drawpixel(mode_col++, mode_row);
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width)
		    {
			mode_col = 0;
			mode_row++;
		    }
		}
	    }
	    else
	    {
		for (col = 0; col < width; col++)
		{
		    vga_setcolor(GETJSAMPLE(*ptr++));
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width)
		    {
			mode_col = 0;
			mode_row++;
		    }
		}
	    }
	}
    }
    else
    {
	unsigned int* gamma;
	unsigned int r_shift, g_shift;
	if (mode_depth == 32768)
	{
	    gamma = gamma_table_shift3;
	    r_shift = 10;
	    g_shift = 5;
	}
	else
	{
	    gamma = gamma_table;
	    r_shift = 16;
	    g_shift = 8;
	}
	
	for (row = 0; row < height; row++)
	{
	    int start_pos =
		(start_row + row + centre_row) * logical_byte_width;
	    mode_row = start_pos / mode_linewidth;
	    mode_col = ((start_pos % mode_linewidth) / bytesperpixel)
		+ centre_col;
	    ptr = pixel_data[row];

	    /*
	     * Duplicated code so we only do an unnecessary test per line
	     * rather than per pixel.
	     */
	    if (opt_doublex)
	    {
		for (col = 0; col < width; col++)
		{
		    r = gamma[GETJSAMPLE(*ptr++)];
		    g = gamma[GETJSAMPLE(*ptr++)];
		    b = gamma[GETJSAMPLE(*ptr++)];
		    vga_setcolor(r << r_shift | g << g_shift | b);
		    vga_drawpixel(mode_col++, mode_row);
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width)
		    {
			mode_col = 0;
			mode_row++;
		    }
		}
	    }
	    else
	    {
		for (col = 0; col < width; col++)
		{
		    r = gamma[GETJSAMPLE(*ptr++)];
		    g = gamma[GETJSAMPLE(*ptr++)];
		    b = gamma[GETJSAMPLE(*ptr++)];
		    vga_setcolor(r << r_shift | g << g_shift | b);
		    vga_drawpixel(mode_col++, mode_row);
		    if (mode_col == mode_width)
		    {
			mode_col = 0;
			mode_row++;
		    }
		}
	    }
	}
    }
    start_row += num_rows;
}


#define OFFSET_ROWS 8
#define OFFSET_COLS PIX_ALIGN	/* a high PIX_ALIGN is a pain here */


void
scroll_until_end(void)
{
    int c = 1, offset = 0;
    bool done = FALSE;
    int max_linestart_offset;
    long sec_diff, usec_diff;
    struct timeval tv_start, tv_end;
    
    max_linestart_offset = (logical_height - mode_height) * logical_byte_width;

    if (opt_slideshow >= 0)
    {
	gettimeofday(&tv_start, NULL);
    }
    while (!done)
    {
	if (c == 0)
	{
	    struct timeval tv = { 0, 10000 };
	    select(0, NULL, NULL, NULL, &tv);
	}
	c = vga_getkey();
	switch (c)
	{
	    case 0:
		break;
	    
	    case 'q':
	    case 'Q':
		opt_cycle = 0;
		next_image = NO_IMAGE;
		done = TRUE;
		break;
	    
	    case 0x1b:		/* ESC */
		c = vga_getkey();
		if (c == '[' || c == 'O')
		{
		    c = vga_getch();
		    switch (c)
		    {
			case 0:
			    break;
		    
			case 'A':	/* up */
			    if (offset > 0)
			    {
				offset -= OFFSET_ROWS * logical_byte_width;
				while (offset < 0)
				{
				    offset += logical_byte_width;
				}
				vga_setdisplaystart(offset);
			    }
			    break;

			case 'B':	/* down */
			    if (offset < max_linestart_offset)
			    {
				offset += OFFSET_ROWS * logical_byte_width;
				while (offset >= (max_linestart_offset
						  + logical_byte_width))
				{
				    offset -= logical_byte_width;
				}
#if 0
				printf("offset = %d, row = %d\n",
				       offset, offset / logical_byte_width);
#endif
				vga_setdisplaystart(offset);
			    }
			    break;

			case 'C':	/* right */
			    if (((offset % logical_byte_width) / bytesperpixel
				 + mode_width)
				< logical_width)
			    {
				offset += MIN(OFFSET_COLS * bytesperpixel,
					      ((logical_width - mode_width)
					       * bytesperpixel)
					      - offset % logical_byte_width);
				vga_setdisplaystart(offset);
			    }
			    break;

			case 'D':	/* left */
			    if (offset % logical_byte_width > 0)
			    {
				offset -= MIN(OFFSET_COLS * bytesperpixel,
					      offset % logical_byte_width);
				vga_setdisplaystart(offset);
			    }
			    break;

			default:
			    done = TRUE;
			    break;
		    }
		}
		else if (c != 0)
		{
		    done = TRUE;
		}
		break;

	    case 0x7f:		/* BACKSPACE */
		done = TRUE;
		next_image = PREVIOUS_IMAGE;
		break;

	    case 'r':
	    case 'R':
		done = TRUE;
		next_image = SAME_IMAGE;
		break;

	    default:
		done = TRUE;
		break;
	}

	if (opt_slideshow >= 0)
	{
	    gettimeofday(&tv_end, NULL);
	    sec_diff = tv_end.tv_sec - tv_start.tv_sec;
	    usec_diff = tv_end.tv_usec - tv_start.tv_usec;
	    done = ((sec_diff > opt_slideshow
		     || (sec_diff == opt_slideshow && usec_diff >= 0))
		    ? TRUE : FALSE);
	}
    }
}


void
display_shutdown(void)
{
    vga_setmode(TEXT);
}
