/******************************************************************************
 *
 *	Lee J Haywood's SliMer - slicing/merging of photographs.
 *	Copyright (C) 2010 by Lee J Haywood.
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version 3
 *	of the License, or (at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *****************************************************************************/

#include <stdio.h>
#include <math.h>
#include <errno.h>
#include <dirent.h>
#include <time.h>
#include <sys/stat.h>

#include <SDL.h>
#include <SDL_endian.h>
#include <SDL_image.h>



/* Size of preview window (fixed).  */

#define MAX_PREVIEW_HEIGHT 600
#define MAX_PREVIEW_WIDTH 800

/* Title to display against window.  */

#define PROGRAM_TITLE "SliMer"

/* Files are expected to have this extension.  */

#define FILE_EXTENSION "jpg"

/* Return values for program when an error occurs.  */

#define USER_ERROR 1
#define SYSTEM_ERROR 2

/* Boolean type and associated values.  */

#define bool int

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif



/* Global variables.  */

static char *Program;
static SDL_Surface *Screen = NULL;
static SDL_Event Event;
static int FullHeight, FullWidth;
static int PreviewHeight, PreviewWidth;
static int RefreshTop, RefreshLeft;
static int RefreshHeight, RefreshWidth;
static SDL_Surface *BaseImage;
static SDL_Surface *LoadedImage;
static Uint8 OldRed, OldGreen, OldBlue;



/* Structure used to list either template names or snapshot titles.  */

struct ListingInfo
    {
	char *FileName;
	struct ListingInfo *Next;
    };



/* Function prototypes.  */

static void Get_File_List( const char *DirectoryName,
			   struct ListingInfo **List, int *NumImages );
static void Free_File_List( struct ListingInfo *List );
static int Parse_Number( const char *Type, const char *String );
static bool Mixed_Case_Match( const char *FirstString,
			      const char *SecondString );
static void Open_Window( void );
static void Set_Title( const char *Suffix );
static int Scale_Value( int Original, int OldScale, int NewScale );
static void Read_Pixel( SDL_Surface *Image, int Row, int Column,
		 Uint8 *Red, Uint8 *Green, Uint8 *Blue );
static void Set_Pixel( SDL_Surface *Image,
		      int Row, int Column, Uint8 Red, Uint8 Green, Uint8 Blue );
static void Do_Refresh( void );
static void Close_Display( void );





/***** MAIN PROCESSING *****/

int main( int argc, char *argv[] )
{
    struct ListingInfo *FileList, *FirstImage, *Entry;
    char Buffer[ 128 + 1 ];
    time_t InitTime, TitleTime;
    int Elapsed, Remaining, LastRemaining;
    int Skip, Total;
    int LoadTotal, LoadNum;
    int ScanLimit, CopyLimit;
    int CopyRow = 0, CopyColumn = 0;
    int PreviewRow, PreviewColumn;
    int Index, Count;
    int NumImages, ImageNum, LastImage;
    char Mode, Direction;

    /* Construct a program name for error messages.  */
#ifdef WINDOWS
    if ( strchr( argv[ 0 ], '\\' ) == NULL )
    {
	Program = argv[ 0 ];
    }
    else
    {
	Program = strrchr( argv[ 0 ], '\\' ) + 1;
    }
#else
    if ( strchr( argv[ 0 ], '/' ) == NULL )
    {
	Program = argv[ 0 ];
    }
    else
    {
	Program = strrchr( argv[ 0 ], '/' ) + 1;
    }
#endif

    /* Validate usage.  */
    if ( argc < 3 || argc > 7 )
    {
	fprintf( stderr, "Usage: %s <Directory> <File> [ H | V ]\n"
			 "Usage: %s <Directory> <File> { H | V } [ F | R ]\n"
			 "Usage: %s <Directory> <File> { H | V } { F | R } "
				   "<Skip> [ <Count ]\n",
			 Program, Program, Program );
	return USER_ERROR;
    }

    /* Ensure that the horizontal/vertical switch is valid if specified, or
     * default to horizontal slicing.  */
    Mode = 'H';
    if ( argc > 3 )
    {
	Mode = (char) toupper( (int) argv[ 3 ][ 0 ] );
	if ( strlen( argv[ 3 ] ) != 1 || ( Mode != 'H' && Mode != 'V' ) )
	{
	    fprintf( stderr,
		"%s: Mode must be H (horizontal) or V (vertical).\n", Program );
	    return USER_ERROR;
	}
    }

    /* Ensure that the forwards/reverse switch is valid if specified, or
     * default to a forwards scan.  */
    Direction = 'F';
    if ( argc > 4 )
    {
	Direction = (char) toupper( (int) argv[ 4 ][ 0 ] );
	if ( strlen( argv[ 4 ] ) != 1 ||
	     ( Direction != 'F' && Direction != 'R' ) )
	{
	    fprintf( stderr,
		     "%s: Direction must be F (forwards) or R (reverse).\n",
		     Program );
	    return USER_ERROR;
	}
    }

    /* If a skip number was given, parse/validate its numeric value.  */
    Skip = 0;
    if ( argc > 5 )
    {
	Skip = Parse_Number( "Skip", argv[ 5 ] );
	if ( Skip < 0 )
	{
	    return USER_ERROR;
	}
    }

    /* If a count was given, parse/validate its numeric value.  */
    Total = 0;
    if ( argc > 6 )
    {
	/* Ensure that it is a valid number.  */
	Total = Parse_Number( "Count", argv[ 6 ] );
	if ( Total < 0 )
	{
	    return USER_ERROR;
	}
    }

    /* Access the SDL library.  */
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
	fprintf( stderr, "%s: Unable to initialise SDL library,\n\t%s",
			 Program, SDL_GetError() );
	return SYSTEM_ERROR;
    }

    /* Ensure that images are freed and SDL is closed when the program ends.  */
    if ( atexit( Close_Display ) )
    {
	fprintf( stderr,
		 "%s: Failed to register graphics shutdown handler: %s.",
	         Program, strerror( errno ) );
    }

    /* Get the list of image files to load.  */
    Get_File_List( argv[ 1 ], &FileList, &NumImages );
    if ( FileList == NULL )
    {
	fprintf( stderr, "%s: No .%s files found in '%s'.\n",
			 Program, FILE_EXTENSION, argv[ 1 ] );
	return USER_ERROR;
    }

    /* If images are to be skipped, find the first image to load.  */
    FirstImage = FileList;
    while ( Skip-- > 0 )
    {
	FirstImage = FirstImage->Next;
	if ( FirstImage == NULL )
	{
	    fprintf( stderr, "%s: All images skipped!\n", Program );
	    Free_File_List( FileList );
	    return USER_ERROR;
	}
	NumImages--;
    }

    /* Limit the number of images to actually load if required.  */
    if ( Total > 0 && Total < NumImages )
    {
	NumImages = Total;
    }

    /* Start the timer.  */
    InitTime = time( NULL );

    /* Load the first image, to use as the base image.  */
    printf( "0 %s\n", FirstImage->FileName );
    fflush( stdout );
    BaseImage = IMG_Load( FirstImage->FileName );
    if ( BaseImage == NULL )
    {
	fprintf( stderr, "%s: Failed to load base image '%s'.\n%s: %s\n",
		 Program, FirstImage->FileName, Program, SDL_GetError() );
	Free_File_List( FileList );
	return SYSTEM_ERROR;
    }

    /* Get the height/width from the base image.  */
    FullHeight = BaseImage->h;
    FullWidth = BaseImage->w;
    if ( Mode == 'H' )
    {
	ScanLimit = FullWidth;
	CopyLimit = FullHeight;
    }
    else
    {
	ScanLimit = FullHeight;
	CopyLimit = FullWidth;
    }

    /* Determine a preview size based on the full image size.  */
    PreviewHeight = FullHeight;
    PreviewWidth = FullWidth;
    if ( PreviewHeight > MAX_PREVIEW_HEIGHT )
    {
	PreviewHeight = MAX_PREVIEW_HEIGHT;
	PreviewWidth = Scale_Value( PreviewWidth,
				    FullHeight, MAX_PREVIEW_HEIGHT );
    }
    if ( PreviewWidth > MAX_PREVIEW_WIDTH )
    {
	PreviewHeight = Scale_Value( PreviewHeight,
				     FullWidth, MAX_PREVIEW_WIDTH );
	PreviewWidth = MAX_PREVIEW_WIDTH;
    }

    /* Prepare to do a full refresh.  */
    RefreshTop = 0;
    RefreshLeft = 0;
    RefreshHeight = PreviewHeight;
    RefreshWidth = PreviewWidth;

    /* Open the preview window.  */
    Screen = NULL;
    if ( PreviewHeight > 0 && PreviewWidth > 0 )
    {
	Open_Window();
    }

    /* Count how many images will be loaded, to account for any skipped.  */
    LoadTotal = 0;
    LastImage = -1;
    for ( Index = 0; Index < ScanLimit; Index++ )
    {
	ImageNum = (int) ( ( ( (long long) Index ) * (long long) NumImages ) /
			   (long long) ScanLimit );
	if ( ImageNum != LastImage )
	{
	    LoadTotal++;
	    LastImage = ImageNum;
	}
    }

    /* For each slice...  */
    LastRemaining = 0;
    TitleTime = time( NULL );
    LoadNum = 1;
    LoadedImage = BaseImage;
    LastImage = 0;
    for ( Index = 0; Index < ScanLimit; Index++ )
    {
	/* Work out which image to take the slice from.  */
	ImageNum = (int) ( ( ( (long long) Index ) * (long long) NumImages ) /
			   (long long) ScanLimit );

	/* If a different image needs to be loaded...  */
	if ( ImageNum != LastImage )
	{
	    /* Display the progress so far.  */
	    Do_Refresh();

	    /* Determine out how much time has elapsed so far, in seconds.  */
	    Elapsed = (int) difftime( time( NULL ), InitTime );

	    /* Estimate how many seconds it will take to do the rest.  Assume
	     * that saving the result is equivalent to loading one image.  */
	    Remaining =
		( (int) ( ( ( ( (long long) ( ( LoadTotal + 1 ) - LoadNum ) ) *
			      (long long) Elapsed ) /
			    (long long) LoadNum ) ) ) + 1;

	    /* Display the estimated time remaining in minutes and seconds,
	     * but avoid showing increases unless they are persistent.  */
	    if ( Remaining < LastRemaining ||
		 (int) difftime( time( NULL ), TitleTime ) > 5 )
	    {
		sprintf( Buffer, "%d:%02d", Remaining / 60, Remaining % 60 );
		Set_Title( Buffer );
		TitleTime = time( NULL );
		LastRemaining = Remaining;
	    }

	    /* Deallocate memory used by the previous image.  */
	    if ( LoadedImage != BaseImage )
	    {
		SDL_FreeSurface( LoadedImage );
	    }
	    LastImage = ImageNum;

	    /* Check to see if the user wants the program to terminate.  */
	    while ( SDL_PollEvent( &Event ) )
	    {
		if ( Event.type == SDL_QUIT )
		{
		    fprintf( stderr, "%s: Processing aborted.\n", Program );
		    Free_File_List( FileList );
		    return 0;
		}
	    }

	    /* Locate the file name of the image to be loaded.  */
	    Count = 0;
	    for ( Entry = FirstImage;
		  Count < ImageNum && Entry != NULL; Entry = Entry->Next )
	    {
		Count++;
	    }
	    if ( Entry == NULL )
	    {
		fprintf( stderr, "%s: Internal counting error.\n", Program );
		Free_File_List( FileList );
		return SYSTEM_ERROR;
	    }
	    printf( "%d %s\n", Index, Entry->FileName );
	    fflush( stdout );

	    /* Load the new image.  */
	    LoadedImage = IMG_Load( Entry->FileName );
	    if ( LoadedImage == NULL )
	    {
		fprintf( stderr, "%s: Failed to load '%s'.\n",
				 Program, Entry->FileName );
		Free_File_List( FileList );
		return SYSTEM_ERROR;
	    }
	    LoadNum++;

	    /* Ensure that all of the images have the same resolution.  */
	    if ( LoadedImage->h != FullHeight || LoadedImage->w != FullWidth )
	    {
		fprintf( stderr, "%s: Image '%s' has a different resolution "
				 "(%dx%d) to image '%s' (%dx%d).\n",
				 Program, Entry->FileName,
				 LoadedImage->w, LoadedImage->h,
			 FirstImage->FileName, FullWidth, FullHeight );
		Free_File_List( FileList );
		return USER_ERROR;
	    }
	}

	/* Select the row/column number based on slice direction.  */
	if ( Mode == 'H' )
	{
	    if ( Direction == 'R' )
	    {
		CopyColumn = ( FullWidth - 1 ) - Index;
	    }
	    else
	    {
		CopyColumn = Index;
	    }
	}
	else
	{
	    if ( Direction == 'R' )
	    {
		CopyRow = ( FullHeight - 1 ) - Index;
	    }
	    else
	    {
		CopyRow = Index;
	    }
	}

	/* For each pixel along the slice being copied...  */
	for ( Count = 0; Count < CopyLimit; Count++ )
	{
	    /* Select the row/column number based on slice direction.  */
	    if ( Mode == 'H' )
	    {
		CopyRow = Count;
	    }
	    else
	    {
		CopyColumn = Count;
	    }

	    /* Copy the pixel from the source image to the base image.  */
	    Read_Pixel( LoadedImage, CopyRow, CopyColumn,
				     &OldRed, &OldGreen, &OldBlue );
	    Set_Pixel( BaseImage, CopyRow, CopyColumn,
				  OldRed, OldGreen, OldBlue );

	    /* If the preview window is active...  */
	    if ( Screen != NULL )
	    {
		/* Copy the pixel to the preview window.  */
		PreviewRow = Scale_Value( CopyRow, FullHeight, PreviewHeight ),
		PreviewColumn = Scale_Value( CopyColumn,
					     FullWidth, PreviewWidth ),
		Set_Pixel( Screen, PreviewRow, PreviewColumn,
			   OldRed, OldGreen, OldBlue );

		/* Move to the empty gap next to the current preview detail.  */
		if ( Mode == 'H' )
		{
		    if ( Direction == 'R' )
		    {
			PreviewColumn--;
		    }
		    else
		    {
			PreviewColumn++;
		    }
		}
		else if ( Direction == 'R' )
		{
		    PreviewRow--;
		}
		else
		{
		    PreviewRow++;
		}

		/* Add a marker line to show the edge of the updated area.  */
		if ( PreviewRow >= 0 && PreviewRow < PreviewHeight &&
		     PreviewColumn >= 0 && PreviewColumn < PreviewWidth )
		{
		    Set_Pixel( Screen, PreviewRow, PreviewColumn, 255, 0, 128 );
		}
	    }
	}
    }

    /* Deallocate the final image, if not the base image.  */
    if ( LoadedImage != BaseImage )
    {
	SDL_FreeSurface( LoadedImage );
    }

    /* Deallocate the list of files.  */
    Free_File_List( FileList );

    /* Complete the preview.  */
    Do_Refresh();

    /* Save the base image and then deallocate its memory.  */
    Set_Title( "saving..." );
    SDL_SaveBMP( BaseImage, argv[ 2 ] );
    SDL_FreeSurface( BaseImage );

    /* Return successfully.  */
    return 0;
}



/* Provides a list of image files in the specified directory, sorted into name
 * order.  */

static void Get_File_List( const char *DirectoryName,
			   struct ListingInfo **List, int *NumImages )
{
    DIR *Handle;
    struct dirent *DirEntry;
    struct stat FileInfo;
    struct ListingInfo *Result, *OldEntry, *LastEntry, *NewEntry;
    char *ObjectName;
    int Length;

    /* Return no list by default.  */
    *List = NULL;
    *NumImages = 0;

    /* Open the directory to be listed.  */
    Handle = opendir( DirectoryName );
    if ( Handle == NULL )
    {
	fprintf( stderr, "%s: Failed to open directory '%s': %s.\n",
			 Program, DirectoryName, strerror( errno ) );
	return;
    }

    /* For each entry in the directory...  */
    Result = NULL;
    do
    {
	/* If a directory entry can be read and has the correct extension...  */
	DirEntry = readdir( Handle );
	if ( DirEntry != NULL )
	{
	    Length = strlen( DirEntry->d_name ) -
		     ( strlen( FILE_EXTENSION ) + 1 ) ;
	    if ( DirEntry->d_name[ Length ] == '.' &&
		 Mixed_Case_Match( &DirEntry->d_name[ Length + 1 ],
				   FILE_EXTENSION ) )
	    {
		/* Allocate space for a copy of the object's name.  */
		ObjectName = malloc( strlen( DirectoryName ) +
				   strlen( DirEntry->d_name ) + (size_t) 2 );
		if ( ObjectName == NULL )
		{
		    fprintf( stderr,
				"Failed to allocate memory for file name.\n" );
		    Free_File_List( Result );
		    return;
		}

		/* Examine the object.  */
		sprintf( ObjectName, "%s/%s",
				   DirectoryName, DirEntry->d_name );
		if ( stat( ObjectName, &FileInfo ) != 0 )
		{
		    fprintf( stderr, "Failed to examine '%s': %s.",
				     ObjectName, strerror( errno ) );
		    Free_File_List( Result );
		    return;
		}

		/* If the entry really is a file...  */
		if ( S_ISREG( FileInfo.st_mode ) )
		{
		    /* Allocate an entry for the file.  */
		    NewEntry = (struct ListingInfo *)
				malloc( sizeof ( struct ListingInfo ) );
		    if ( NewEntry == NULL )
		    {
			fprintf( stderr,
				  "Failed to allocate memory for file list." );
			(void) closedir( Handle );
			Free_File_List( Result );
			return;
		    }


		    /* Insert new entry in result list, keeping the list in
		     * descending order of last modification time.  */
		    NewEntry->FileName = ObjectName;
		    LastEntry = NULL;
		    for ( OldEntry = Result;
			  OldEntry != NULL &&
				strcmp( NewEntry->FileName,
					OldEntry->FileName ) > 0;
			  OldEntry = OldEntry->Next )
		    {
			LastEntry = OldEntry;
		    }
		    if ( LastEntry == NULL )
		    {
			NewEntry->Next = Result;
			Result = NewEntry;
		    }
		    else
		    {
			NewEntry->Next = LastEntry->Next;
			LastEntry->Next = NewEntry;
		    }

		    /* Count the entry.  */
		    ( *NumImages )++;
		}
		/* Otherwise, discard the copy of the object's name.  */
		else
		{
		    free( ObjectName );
		}
	    }
	}
    }
    while ( DirEntry != NULL );

    /* Close the directory.  */
    (void) closedir( Handle );

    /* Provide result, indicating success.  */
    *List = Result;
}



/* Deallocates a list of template names or snapshot titles after use.  */

static void Free_File_List( struct ListingInfo *List )
{
    struct ListingInfo *DeadNode;

    /* Deallocate each entry until the list is empty.  */
    while ( List != NULL )
    {
	DeadNode = List;
	List = List->Next;
	free( DeadNode );
    }
}



/* Converts a string of text into a number, returning a negative value if it
 * is not a valid, non-negative integer.  */

static int Parse_Number( const char *Type, const char *String )
{
    char Buffer[ 64 + 1 ];
    int Number;

    /* Parse the number.  */
    Number = atoi( String );

    /* Fail if the string representation of the number doesn't match the
     * original string.  */
    sprintf( Buffer, "%d", Number );
    if ( strcmp( String, Buffer ) != 0 )
    {
	fprintf( stderr, "%s: %s parameter must be a valid number.\n",
			 Program, Type );
	return -1;
    }

    /* Return the number.  */
    return Number;
}



/* Compares two strings, ignoring the case of both (according to the current
 * locale if supported by the system).  Returns TRUE if the strings are
 * identical.  */

static bool Mixed_Case_Match( const char *FirstString,
			      const char *SecondString )
{
    int Index;

    /* For each character in the first string, including its terminator...  */
    Index = 0;
    do
    {
	/* Return FALSE if characters differ in each string (ignoring case).  */
	if ( tolower( (int) FirstString[ Index ] ) !=
	     tolower( (int) SecondString[ Index ] ) )
	{
	    return FALSE;
	}
    }
    while ( FirstString[ Index++ ] != '\0' );

    /* Return TRUE - the strings are a complete match.  */
    return TRUE;
}



/* Opens the preview window.  */

static void Open_Window( void )
{
    int Row, Column;

    /* Open the graphics display.  NOTE: 24-bit depth is not supported.  */
    Screen = SDL_SetVideoMode( PreviewWidth, PreviewHeight, 16, SDL_SWSURFACE );
    if ( Screen == NULL )
    {
	fprintf( stderr, "%s: Unable to set video mode: %s",
			 Program, SDL_GetError() );
	return;
    }

    /* Set window and icon titles.  */
    Set_Title( NULL );

    /* Ensure that the display is big enough to display everything.  Just
     * don't display a preview if it is too small.  */
    if ( Screen->h != PreviewHeight || Screen->w != PreviewWidth )
    {
	fprintf( stderr,
		 "%s: Display measures %dx%d instead of requested %dx%d.",
		 Program, Screen->w, Screen->h, PreviewWidth, PreviewHeight );

	if ( Screen->h < PreviewHeight || Screen->w < PreviewWidth )
	{
	    Screen = NULL;
	    return;
	}
    }

    /* Copy the base image to the preview window, scaling as necessary and
     * reducing the image brightness, to be overwritten by the actual merge.  */
    for ( Row = 0; Row < FullHeight; Row++ )
    {
	for ( Column = 0; Column < FullWidth; Column++ )
	{
	    Read_Pixel( BaseImage, Row, Column,
				     &OldRed, &OldGreen, &OldBlue );
	    OldRed /= 8;
	    OldGreen /= 8;
	    OldBlue /= 8;
	    Set_Pixel( Screen, Scale_Value( Row, FullHeight, PreviewHeight ),
			       Scale_Value( Column, FullWidth, PreviewWidth ),
			       OldRed, OldGreen, OldBlue );
	}
    }

    /* Display the preview.  */
    Do_Refresh();
}



/* Sets the title of the preview window.  */

static void Set_Title( const char *Suffix )
{
    char Title[ 1024 + 1 ];

    strcpy( Title, PROGRAM_TITLE );
    if ( Suffix != NULL )
    {
	strcat( Title, " - " );
	strcat( Title, Suffix );
    }
    SDL_WM_SetCaption( Title, PROGRAM_TITLE );
}



/* Calculates a co-ordinate based on the given scaling.  */

static int Scale_Value( int Original, int OldScale, int NewScale )
{
	return (int) ( ( (long long) Original * (long long) NewScale ) /
		       (long long) OldScale );
}



/* Reads a given pixel from the image specified, returning its red, green and
 * blue values.  */

static void Read_Pixel( SDL_Surface *Image, int Row, int Column,
			Uint8 *Red, Uint8 *Green, Uint8 *Blue )
{
    SDL_PixelFormat *Format;
    Uint32 Value, Colour;

    /* Find the pixel to be read, get its combined colour value.  */
    Format = Image->format;
    SDL_LockSurface( Image );
    Colour = *( (Uint32 *) &( ( (Uint8 *) Image->pixels )
				    [ 3 * ( ( Row * Image->w ) + Column ) ] ) );
    SDL_UnlockSurface( Image );

    /* Get red component.  */
    Value = Colour & Format->Rmask;
    Value = Value >> Format->Rshift;
    *Red = (Uint8) Value;

    /* Get green component.  */
    Value = Colour & Format->Gmask;
    Value = Value >> Format->Gshift;
    *Green = (Uint8) Value;

    /* Get blue component.  */
    Value = Colour & Format->Bmask;
    Value = Value >> Format->Bshift;
    *Blue = (Uint8) Value;
}



/* Overwrites a given pixel in the specified image with the red, green and blue
 * values provided.  The display needs to be refreshed afterwards.  */

static void Set_Pixel( SDL_Surface *Image,
		       int Row, int Column, Uint8 Red, Uint8 Green, Uint8 Blue )
{
    Uint32 Colour;
    Uint8 *bufp8;
    Uint16 *bufp16;
    Uint32 *bufp32;

    /* Convert the colour components to a single value.  */
    Colour = SDL_MapRGB( Image->format, Red, Green, Blue );

    /* Base drawing method on image depth...  */
    switch ( Image->format->BytesPerPixel )
    {
	/* If 8 bits per pixel, just write 8 bit colour value.  */
	case 1 :
	    bufp8 = (Uint8 *) Image->pixels + ( Row * Image->pitch ) + Column;
	    *bufp8 = (Uint8) Colour;
	    break;

	/* If 15 or 16 bits per pixel, write 16 bit colour value.  */
	case 2 :
	    bufp16 = (Uint16 *) Image->pixels +
				( Row * Image->pitch / 2 ) + Column;
	    *bufp16 = (Uint16) Colour;
	    break;

	/* If 24 bits per pixel, write red, green and blue components.  */
	case 3 :
	    bufp8 = &( ( (Uint8 *) Image->pixels )
				[ 3 * ( ( Row * Image->w ) + Column ) ] );
	    if ( SDL_BYTEORDER == SDL_LIL_ENDIAN )
	    {
		bufp8[ 0 ] = (Uint8) Colour;
		bufp8[ 1 ] = (Uint8) ( Colour >> 8 );
		bufp8[ 2 ] = (Uint8) ( Colour >> 16 );
	    }
	    else
	    {
		bufp8[ 2 ] = (Uint8) Colour;
		bufp8[ 1 ] = (Uint8) ( Colour >> 8 );
		bufp8[ 0 ] = (Uint8) ( Colour >> 16 );
	    }
	    break;

	/* If 32 bits per pixel, write combined 32 bit colour value.  */
	case 4 :
	    bufp32 = (Uint32 *) Image->pixels +
				( Row * Image->pitch / 4 ) + Column;
	    *bufp32 = Colour;
	    break;
    }

    /* Update the position/size of the area to be refreshed.  */
    if ( Image == Screen )
    {
	if ( Row < RefreshTop )
	{
	    if ( RefreshHeight == 0 )
	    {
		RefreshHeight = 1;
	    }
	    else
	    {
		RefreshHeight += RefreshTop - Row;
	    }
	    RefreshTop = Row;
	}
	if ( Column < RefreshLeft )
	{
	    if ( RefreshWidth == 0 )
	    {
		RefreshWidth = 1;
	    }
	    else
	    {
		RefreshWidth += RefreshLeft - Column;
	    }
	    RefreshLeft = Column;
	}
	if ( Row >= RefreshTop + RefreshHeight )
	{
	    RefreshHeight = ( Row + 1 ) - RefreshTop;
	}
	if ( Column >= RefreshLeft + RefreshWidth )
	{
	    RefreshWidth = ( Column + 1 ) - RefreshLeft;
	}
    }
}



/* Refreshes the area of the preview window which has changed.  */

static void Do_Refresh( void )
{
    /* If the preview window is active and the refresh area is valid,
     * update the display in that area.  */
    if ( Screen != NULL )
    {
	if ( RefreshHeight > 0 && RefreshWidth > 0 &&
	     RefreshTop >= 0 && RefreshLeft >= 0 &&
	     RefreshHeight >= 0 && RefreshWidth >= 0 &&
	     RefreshTop + RefreshHeight <= PreviewHeight &&
	     RefreshLeft + RefreshWidth <= PreviewWidth )
	{
	    SDL_UpdateRect( Screen, (Sint32) RefreshLeft, (Sint32) RefreshTop,
				(Uint32) RefreshWidth, (Uint32) RefreshHeight );
	}
	else
	{
	    fprintf( stderr, "Invalid refresh area (%dx%d / %dx%d).\n",
			RefreshLeft, RefreshTop, RefreshWidth, RefreshHeight );
	}
    }

    /* Prepare for next refresh.  */
    RefreshTop = PreviewHeight;
    RefreshLeft = PreviewWidth;
    RefreshHeight = 0;
    RefreshWidth = 0;
}



/* Deallocates images and closes the SDL interface when the program exits.  */

static void Close_Display( void )
{
    /* Prevent further use of the display.  */
    Screen = NULL;

    /* Close the SDL interface.  */
    SDL_Quit();
}

