/*
  tuxpaint.c
  
  Tux Paint - A simple drawing program for children.
  
  Copyright (c) 2005 by Bill Kendrick and others; see AUTHORS.txt
  bill@newbreedsoftware.com
  http://www.newbreedsoftware.com/tuxpaint/

  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 2 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, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  (See COPYING.txt)
  
  June 14, 2002 - November 20, 2005
*/


/* (Note: VER_VERSION and VER_DATE are now handled by Makefile) */


/* Color depth for Tux Paint to run in, and store canvases in: */

//#define VIDEO_BPP 15 // saves memory
//#define VIDEO_BPP 16 // causes discoloration
//#define VIDEO_BPP 24 // compromise
#define VIDEO_BPP 32 // might be fastest, if conversion funcs removed


// plan to rip this out as soon as it is considered stable
//#define THREADED_FONTS
#define FORKED_FONTS
#ifdef WIN32
#undef FORKED_FONTS
#endif

/* Method for printing images: */

#define PRINTMETHOD_PS             /* Direct to PostScript */
//#define PRINTMETHOD_PNM_PS       /* Output PNM, assuming it gets printed */
//#define PRINTMETHOD_PNG_PNM_PS   /* Output PNG, assuming it gets printed */


/* Default print and alt-print command, depending on the print method: */

#define DEFAULT_PRINTCOMMAND "lpr"
#define DEFAULT_ALTPRINTCOMMAND "kprinter"

#ifdef PRINTMETHOD_PNG_PNM_PS
#define PRINTCOMMAND "pngtopnm | pnmtops | " DEFAULT_PRINTCOMMAND
#elif defined(PRINTMETHOD_PNM_PS)
#define PRINTCOMMAND "pnmtops | " DEFAULT_PRINTCOMMAND
#elif defined(PRINTMETHOD_PS)
#define PRINTCOMMAND DEFAULT_PRINTCOMMAND
#else
#error No print method defined!
#endif

#ifdef PRINTMETHOD_PNG_PNM_PS
#define ALTPRINTCOMMAND "pngtopnm | pnmtops | " DEFAULT_ALTPRINTCOMMAND
#elif defined(PRINTMETHOD_PNM_PS)
#define ALTPRINTCOMMAND "pnmtops | " DEFAULT_ALTPRINTCOMMAND
#elif defined(PRINTMETHOD_PS)
#define ALTPRINTCOMMAND DEFAULT_ALTPRINTCOMMAND
#else
#error No alt print method defined!
#endif


/* Compile-time options: */

/* #define DEBUG */
/* #define DEBUG_MALLOC */
/* #define LOW_QUALITY_THUMBNAILS */
/* #define LOW_QUALITY_COLOR_SELECTOR */
/* #define LOW_QUALITY_STAMP_OUTLINE */
/* #define LOW_QUALITY_FLOOD_FILL */
/* #define NO_PROMPT_SHADOWS */
/* #define USE_HWSURFACE */

/* Disable fancy cursors in fullscreen mode, to avoid SDL bug: */
/* (This bug is still around, as of SDL 1.2.9, October 2005) */
#define LARGE_CURSOR_FULLSCREEN_BUG

// control the color selector
#define COLORSEL_DISABLE 0  // disable and draw the (greyed out) colors
#define COLORSEL_ENABLE  1  // enable and draw the colors
#define COLORSEL_CLOBBER 2  // colors get scribbled over
#define COLORSEL_REFRESH 4  // redraw the colors, either on or off

static unsigned draw_colors(unsigned action);

/////////////////////////////////////////////////////////////////////
// hide all scale-related values here

typedef struct scaleparams {
  unsigned numer, denom;
} scaleparams;
static scaleparams scaletable[] = {
  {  1,256}, //  0.00390625
  {  3,512}, //  0.005859375
  {  1,128}, //  0.0078125
  {  3,256}, //  0.01171875
  {  1, 64}, //  0.015625
  {  3,128}, //  0.0234375
  {  1, 32}, //  0.03125
  {  3, 64}, //  0.046875
  {  1, 16}, //  0.0625
  {  3, 32}, //  0.09375
  {  1,  8}, //  0.125
  {  3, 16}, //  0.1875
  {  1,  4}, //  0.25
  {  3,  8}, //  0.375
  {  1,  2}, //  0.5
  {  3,  4}, //  0.75
  {  1,  1}, //  1
  {  3,  2}, //  1.5
  {  2,  1}, //  2
  {  3,  1}, //  3
  {  4,  1}, //  4
  {  6,  1}, //  6
  {  8,  1}, //  8
  { 12,  1}, // 12
  { 16,  1}, // 16
  { 24,  1}, // 24
  { 32,  1}, // 32
  { 48,  1}, // 48
};


/* Macros: */

#define HARD_MIN_STAMP_SIZE 0  // bottom of scaletable
#define HARD_MAX_STAMP_SIZE (sizeof scaletable / sizeof scaletable[0] - 1)

#define MIN_STAMP_SIZE (stamp_data[cur_stamp]->min)
#define MAX_STAMP_SIZE (stamp_data[cur_stamp]->max)

// to scale some offset, in pixels, like the current stamp is scaled
#define SCALE_LIKE_STAMP(x) ( ((x) * scaletable[stamp_data[cur_stamp]->size].numer + scaletable[stamp_data[cur_stamp]->size].denom-1) / scaletable[stamp_data[cur_stamp]->size].denom )
// pixel dimensions of the current stamp, as scaled
#define CUR_STAMP_W SCALE_LIKE_STAMP(active_stamp->w)
#define CUR_STAMP_H SCALE_LIKE_STAMP(active_stamp->h)

///////////////////////////////////////////////////////////////////////////////


#define REPEAT_SPEED 300  /* Initial repeat speed for scrollbars */
#define CURSOR_BLINK_SPEED 500  /* Initial repeat speed for cursor */

#include <stdio.h>
#include <stdlib.h>
#define __USE_GNU  /* for strcasestr() */
#include <string.h>
#include <ctype.h>
#include <time.h>

// math.h makes y1 an obscure function!
#define y1 evil_y1
#include <math.h>
#undef y1

#include <locale.h>

#ifndef OLD_UPPERCASE_CODE
#include <wctype.h>
#endif

#include <libintl.h>
#ifndef gettext_noop
#define gettext_noop(String) String
#endif


#ifdef DEBUG
#define gettext(String) debug_gettext(String)
#endif


#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#include <sys/types.h>
#include <sys/stat.h>

#ifndef WIN32
#include <unistd.h>
#include <dirent.h>
#include <signal.h>
#ifdef __BEOS__
#include "BeOS_print.h"
// workaround dirent handling bug in TuxPaint code
typedef struct safer_dirent {
       dev_t                   d_dev;
       dev_t                   d_pdev;
       ino_t                   d_ino;
       ino_t                   d_pino;
       unsigned short  d_reclen;
       char                    d_name[NAME_MAX];
} safer_dirent;
#define dirent safer_dirent
#endif
#ifdef __APPLE__
#include "macosx_print.h"
#include "wrapperdata.h"
extern WrapperData macosx;
#endif
#else

#include <unistd.h>
#include <dirent.h>
#include <malloc.h>
#include "win32_print.h"
#include <io.h>
#include <direct.h>

#define mkdir(path,access)    _mkdir(path)

#endif /* WIN32 */

#include <errno.h>
#include <sys/stat.h>

#include "SDL.h"
#if !defined(_SDL_H)
#error "---------------------------------------------------"
#error "If you installed SDL from a package, be sure to get"
#error "the development package, as well!"
#error "(e.g., 'libsdl1.2-devel.rpm')"
#error "---------------------------------------------------"
#endif

#include "SDL_image.h"
#if !defined(_SDL_IMAGE_H) && !defined(_IMG_h)
#error "---------------------------------------------------"
#error "If you installed SDL_image from a package, be sure"
#error "to get the development package, as well!"
#error "(e.g., 'libsdl-image1.2-devel.rpm')"
#error "---------------------------------------------------"
#endif

#include "SDL_ttf.h"
#if !defined(_SDL_TTF_H) && !defined(_SDLttf_h)
#error "---------------------------------------------------"
#error "If you installed SDL_ttf from a package, be sure"
#error "to get the development package, as well!"
#error "(e.g., 'libsdl-ttf1.2-devel.rpm')"
#error "---------------------------------------------------"
#endif

#ifndef NOSOUND
#include "SDL_mixer.h"
#if !defined(_SDL_MIXER_H) && !defined(_MIXER_H_)
#error "---------------------------------------------------"
#error "If you installed SDL_mixer from a package, be sure"
#error "to get the development package, as well!"
#error "(e.g., 'libsdl-mixer1.2-devel.rpm')"
#error "---------------------------------------------------"
#endif
#endif

#ifndef SAVE_AS_BMP
#include <png.h>
#define FNAME_EXTENSION ".png"
#ifndef PNG_H
#error "---------------------------------------------------"
#error "If you installed the PNG libraries from a package,"
#error "be sure to get the development package, as well!"
#error "(e.g., 'libpng2-devel.rpm')"
#error "---------------------------------------------------"
#endif
#else
#define FNAME_EXTENSION ".bmp"
#endif

#if defined(THREADED_FONTS) || defined(THREADED_STAMPS)
#include "SDL_thread.h"
#include "SDL_mutex.h"
#else
#define SDL_CreateThread(fn,vp) (void*)(long)(fn(vp))
#define SDL_WaitThread(tid,rcp) do{(void)tid;(void)rcp;}while(0)
#define SDL_Thread int
#define SDL_mutex int
#define SDL_CreateMutex() 0  // creates in released state
#define SDL_DestroyMutex(lock)
#define SDL_mutexP(lock)  // take lock
#define SDL_mutexV(lock)  // release lock
#endif

#ifndef FORKED_FONTS
static SDL_Thread *font_thread;
#endif
static volatile long font_thread_done = 0, font_thread_aborted = 0;
static volatile long waiting_for_fonts = 0;
static void run_font_scanner(void);
static int font_scanner_pid;
static int font_socket_fd;

#include "tools.h"
#include "titles.h"
#include "colors.h"
#include "shapes.h"
#include "magic.h"
#include "sounds.h"
#include "tip_tux.h"
#include "great.h"


#include "watch.xbm"
#include "watch-mask.xbm"

#include "hand.xbm"
#include "hand-mask.xbm"

#include "wand.xbm"
#include "wand-mask.xbm"

#include "insertion.xbm"
#include "insertion-mask.xbm"

#include "brush.xbm"
#include "brush-mask.xbm"

#include "crosshair.xbm"
#include "crosshair-mask.xbm"

#include "rotate.xbm"
#include "rotate-mask.xbm"

#include "up.xbm"
#include "up-mask.xbm"

#include "down.xbm"
#include "down-mask.xbm"

#include "tiny.xbm"
#include "tiny-mask.xbm"

#include "arrow.xbm"
#include "arrow-mask.xbm"

#ifdef DEBUG_MALLOC
#include "malloc.c"
#endif

#ifdef WIN32
/*
  The SDL stderr redirection trick doesn't seem to work for perror().
  This does pretty much the same thing.
*/
static void win32_perror(const char * const str)
{
  if ( str && *str )
    fprintf(stderr,"%s : ",str);
  fprintf(stderr,
	  "%s [%d]\n",
	  (errno<_sys_nerr)?_sys_errlist[errno]:"unknown",errno );
}
#define perror         win32_perror

/*
  WIN32 and MINGW don't have strcasestr().
*/
#define NOMINMAX
#include "Shlwapi.h"
#define strcasestr StrStrI
#endif /* WIN32 */


#ifdef __GNUC__
// This version has strict type checking for safety.
// See the "unnecessary" pointer comparison. (from Linux)
#define min(x,y) ({ \
  typeof(x) _x = (x);     \
  typeof(y) _y = (y);     \
  (void) (&_x == &_y);            \
  _x < _y ? _x : _y; })
#define max(x,y) ({ \
  typeof(x) _x = (x);     \
  typeof(y) _y = (y);     \
  (void) (&_x == &_y);            \
  _x > _y ? _x : _y; })
#else
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif

#define clamp(lo,value,hi)    (min(max(value,lo),hi))


// since gcc-2.5
#ifdef __GNUC__
#define NORETURN __attribute__((__noreturn__))
#define FUNCTION __attribute__((__const__))  // no access to global mem, even via ptr, and no side effect
#else
#define NORETURN
#define FUNCTION
#endif

#if !defined(restrict) && __STDC_VERSION__ < 199901
#if __GNUC__ > 2 || __GNUC_MINOR__ >= 92
#define restrict __restrict__
#else
#warning No restrict keyword?
#define restrict
#endif
#endif

#if __GNUC__ > 2 || __GNUC_MINOR__ >= 96
// won't alias anything, and aligned enough for anything
#define MALLOC __attribute__ ((__malloc__))
// no side effect, may read globals
#define PURE __attribute__ ((__pure__))
// tell gcc what to expect:   if(unlikely(err)) die(err);
#define likely(x)       __builtin_expect(!!(x),1)
#define unlikely(x)     __builtin_expect(!!(x),0)
#define expected(x,y)   __builtin_expect((x),(y))
#else
#define MALLOC
#define PURE
#define likely(x)       (x)
#define unlikely(x)     (x)
#define expected(x,y)   (x)
#endif


/* Unfortunately, there is a bug in SDL_ttf-2.0.6, the current version
   that causes a segmentation fault if an attempt is made to call 
   TTF_OpenFont() with the filename of a font that doesn't exist. This 
   is an old and well documented bug that is fixed in CVS.
*/
static TTF_Font *BUGFIX_TTF_OpenFont206(const char * const file, int ptsize)
{
    FILE    *fp;

    if ((fp = fopen(file, "rb")) == NULL) return NULL;
    fclose(fp);
    return TTF_OpenFont(file, ptsize);
}
#define TTF_OpenFont    BUGFIX_TTF_OpenFont206


#if VIDEO_BPP==32
#ifdef __GNUC__
#define SDL_GetRGBA(p,f,rp,gp,bp,ap) ({ \
  unsigned u_p = p;                     \
  *(ap) = (u_p >> 24) & 0xff;           \
  *(rp) = (u_p >> 16) & 0xff;           \
  *(gp) = (u_p >>  8) & 0xff;           \
  *(bp) = (u_p >>  0) & 0xff;           \
})
#define SDL_GetRGB(p,f,rp,gp,bp) ({ \
  unsigned u_p = p;                     \
  *(rp) = (u_p >> 16) & 0xff;           \
  *(gp) = (u_p >>  8) & 0xff;           \
  *(bp) = (u_p >>  0) & 0xff;           \
})
#endif
#define SDL_MapRGBA(f,r,g,b,a) ( \
  (((a) & 0xffu) << 24)          \
  |                              \
  (((r) & 0xffu) << 16)          \
  |                              \
  (((g) & 0xffu) <<  8)          \
  |                              \
  (((b) & 0xffu) <<  0)          \
)
#define SDL_MapRGB(f,r,g,b) (   \
  (((r) & 0xffu) << 16)          \
  |                              \
  (((g) & 0xffu) <<  8)          \
  |                              \
  (((b) & 0xffu) <<  0)          \
)
#endif


enum {
  SAVE_OVER_PROMPT,
  SAVE_OVER_ALWAYS,
  SAVE_OVER_NO
};

enum {
  ALTPRINT_MOD,
  ALTPRINT_ALWAYS,
  ALTPRINT_NEVER
};


enum {
  STARTER_OUTLINE,
  STARTER_SCENE
};


/* Show debugging stuff: */

static void debug(const char * const str)
{
#ifndef DEBUG
  (void)str;
#else
  fprintf(stderr, "DEBUG: %s\n", str);
  fflush(stderr);
#endif
}

static const char *getfilename(const char* path)
{
  char    *p;

  if ( (p = strrchr( path, '\\' )) != NULL )
    return p+1;
  if ( (p = strrchr( path, '/' )) != NULL )
    return p+1;
  return path;
}


///////////////////////////////////////////////////////////////////
// Language stuff

/* Possible languages: */

enum {
  LANG_AF,     /* Afrikaans */
  LANG_BE,     /* Belarusian */
  LANG_BG,     /* Bulgarian */
  LANG_BR,     /* Breton */
  LANG_CA,     /* Catalan */
  LANG_CS,     /* Czech */
  LANG_CY,     /* Welsh */
  LANG_DA,     /* Danish */
  LANG_DE,     /* German */
  LANG_EL,     /* Greek */
  LANG_EN,     /* English (American) (DEFAULT) */
  LANG_EN_GB,  /* English (British) */
  LANG_ES,     /* Spanish */
  LANG_ES_MX,  /* Spanish (Mexican) */
  LANG_ET,     /* Estonian */
  LANG_EU,     /* Basque */
  LANG_FI,     /* Finnish */
  LANG_FR,     /* French */
  LANG_GA,     /* Gaelic */
  LANG_GL,     /* Galician */
  LANG_GR,     /* Gronings */
  LANG_HE,     /* Hebrew */
  LANG_HI,     /* Hindi */
  LANG_HR,     /* Croatian */
  LANG_HU,     /* Hungarian */
  LANG_I_KLINGON_ROMANIZED,     /* Klingon (Romanized) */
  LANG_ID,     /* Indonesian */
  LANG_IS,     /* Icelandic */
  LANG_IT,     /* Italian */
  LANG_JA,     /* Japanese */
  LANG_KA,     /* Georgian */
  LANG_KO,     /* Korean */
  LANG_LT,     /* Lithuanian */
  LANG_MS,     /* Malay */
  LANG_NB,     /* Norwegian Bokmal */
  LANG_NL,     /* Dutch */
  LANG_NN,     /* Norwegian Nynorsk */
  LANG_PL,     /* Polish */
  LANG_PT_BR,  /* Portuguese (Brazilian) */
  LANG_PT_PT,     /* Portuguese (Portugal) */
  LANG_RO,     /* Romanian */
  LANG_RU,     /* Russian */
  LANG_RW,     /* Kinyarwanda */
  LANG_SK,     /* Slovak */
  LANG_SL,     /* Slovenian */
  LANG_SQ,     /* Albanian */
  LANG_SR,     /* Serbian */
  LANG_SV,     /* Swedish */
  LANG_SW,     /* Swahili */
  LANG_TA,     /* Tamil */
  LANG_TH,     /* Thai */
  LANG_TR,     /* Turkish */
  LANG_UK,     /* Ukrainian */
  LANG_VI,     /* Vietnamese */
  LANG_WA,     /* Walloon */
  LANG_ZH_CN,  /* Chinese (Simplified) */
  LANG_ZH_TW,  /* Chinese (Traditional) */
  NUM_LANGS
};

static const char * lang_prefixes[NUM_LANGS] = {
  "af",
  "be",
  "bg",
  "br",
  "ca",
  "cs",
  "cy",
  "da",
  "de",
  "el",
  "en",
  "en_gb",
  "es",
  "es_mx",
  "et",
  "eu",
  "fi",
  "fr",
  "ga",
  "gl",
  "gos",
  "he",
  "hi",
  "hr",
  "hu",
  "tlh",
  "id",
  "is",
  "it",
  "ja",
  "ka",
  "ko",
  "lt",
  "ms",
  "nb",
  "nl",
  "nn",
  "pl",
  "pt_br",
  "pt_pt",
  "ro",
  "ru",
  "rw",
  "sk",
  "sl",
  "sq",
  "sr",
  "sv",
  "sw",
  "ta",
  "th",
  "tr",
  "uk",
  "vi",
  "wa",
  "zh_cn",
  "zh_tw"
};


// languages which don't use the default font
static int lang_use_own_font[] = {
//  LANG_EL,
//  LANG_HE,
  LANG_HI,
  LANG_JA,
  LANG_KA,
  LANG_KO,
  LANG_TA,
  LANG_TH,
  LANG_ZH_CN,
  LANG_ZH_TW,
  -1
};

static int lang_use_right_to_left[] = {
  LANG_HE,
  -1
};


static int search_int_array(int l, int *array)
{
  int i;

  for (i = 0; array[i] != -1; i++)
    {
      if (array[i] == l)
        return 1;
    }

  return 0;
}

static char * langstr;

static void set_langstr(const char *s)
{
  if (langstr)
    free(langstr);
  langstr = strdup(s);
}

static int need_own_font;
static int need_right_to_left;
static const char * lang_prefix;

/* Determine the current language/locale, and set the language string: */

static void set_current_language(void)
{
  char * loc;
  int lang, i, found;

  bindtextdomain("tuxpaint", LOCALEDIR);
  /* Old version of glibc does not have bind_textdomain_codeset() */
#if defined __GLIBC__ && __GLIBC__ == 2 && __GLIBC_MINOR__ >=2 || __GLIBC__ > 2
  bind_textdomain_codeset("tuxpaint", "UTF-8");
#endif
  textdomain("tuxpaint");

  /* Default... */

  lang = LANG_EN;


#ifndef WIN32
  loc = setlocale(LC_MESSAGES, NULL);
  if (loc != NULL)
    {
      if (strstr(loc, "LC_MESSAGES") != NULL)
	loc = getenv("LANG");
    }
#else
  bind_textdomain_codeset("tuxpaint", "UTF-8");
  loc = getenv("LANGUAGE");
  if (!loc)
    {
      loc = _nl_locale_name(LC_MESSAGES, "");
      if (loc)
        {
          char *s;
          int  len;
          len = strlen("LANGUAGE=")+strlen(loc)+1;
          s = malloc(len);
          snprintf(s, len, "LANGUAGE=%s", loc);
          putenv(s);
        }
    }
#endif

  debug(loc);

  if (loc != NULL)
    {
      /* Which, if any, of the locales is it? */

      found = 0;
      
      for (i = 0; i < NUM_LANGS && found == 0; i++)
	{
	  // Case-insensitive (both "pt_BR" and "pt_br" work, etc.)
      	  if (strncasecmp(loc, lang_prefixes[i], strlen(lang_prefixes[i])) == 0)
	    {
	      lang = i;
	      found = 1;
	    }
	}
    }

  lang_prefix = lang_prefixes[lang];
  need_own_font = search_int_array(lang,lang_use_own_font);
  need_right_to_left = search_int_array(lang,lang_use_right_to_left);

#ifdef DEBUG
  fprintf(stderr,"DEBUG: Language is %s (%d)\n", lang_prefix, lang);
  fflush(stderr);
#endif

}

/* FIXME: All this should REALLY be array-based!!! */
/* Show available languages: */
static void show_lang_usage(FILE * f, const char * const prg)
{
  fprintf(f,
	  "\n"
	  "Usage: %s [--lang LANGUAGE]\n"
	  "\n"
	  "LANGUAGE may be one of:\n"
/* C */	     "  english      american-english\n"
/* af */     "  afrikaans\n"
/* sq */     "  albanian\n"
/* eu */     "  basque       euskara\n"
/* be */     "  belarusian   bielaruskaja\n"
/* nb */     "  bokmal\n"
/* pt_BR */  "  brazilian    brazilian-portuguese   portugues-brazilian\n"
/* br */     "  breton       brezhoneg\n"
/* en_GB */  "  british      british-english\n"
/* bg_BG */  "  bulgarian\n"
/* ca */     "  catalan      catala\n"
/* zh_CN */  "  chinese      simplified-chinese\n"
/* zh_TW */  "               traditional-chinese\n"
/* hr */     "  croatian     hrvatski\n"
/* cs */     "  czech        cesky\n"
/* da */     "  danish       dansk\n"
/* nl */     "  dutch        nederlands\n"
/* et */     "  estonian\n"
/* fi */     "  finnish      suomi\n"
/* fr */     "  french       francais\n"
/* ga */     "  gaelic       gaidhlig\n"
/* gl */     "  galician     galego\n"
/* ka */     "  georgian\n"
/* de */     "  german       deutsch\n"
/* el */     "  greek\n"
/* gos */    "  gronings     zudelk-veenkelonioals\n"
/* he */     "  hebrew\n"
/* hi */     "  hindi\n"
/* hu */     "  hungarian    magyar\n"
/* is */     "  icelandic    islenska\n"
/* id */     "  indonesian   bahasa-indonesia\n"
/* it */     "  italian      italiano\n"
/* ja */     "  japanese\n"
/* rw */     "  kinyarwanda\n"
/* tlh */    "  klingon      tlhIngan\n"
/* ko */     "  korean\n"
/* lt */     "  lithuanian   lietuviu\n"
/* ms */     "  malay\n"
/* es_MX */  "  mexican      mexican-spanish        espanol-mejicano\n"
/* nn */     "  norwegian    nynorsk                norsk\n"
/* pl */     "  polish       polski\n"
/* pt_PT */  "  portuguese   portugues\n"
/* ro */     "  romanian\n"
/* ru */     "  russian      russkiy\n"
/* sr */     "  serbian\n"
/* sk */     "  slovak\n"
/* sl */     "  slovenian    slovensko\n"
/* es */     "  spanish      espanol\n"
/* sw */     "  swahili\n"
/* sv */     "  swedish      svenska\n"
/* ta */     "  tamil\n"
/* th */     "  thai\n"
/* tr */     "  turkish\n"
/* uk */     "  ukrainian\n"
/* vi */     "  vietnamese\n"
/* wa */     "  walloon      walon\n"
/* cy */     "  welsh        cymraeg\n"
	  "\n",
	  prg);
}


/* FIXME: Add accented characters to the descriptions */
/* Show available locales: */
static void show_locale_usage(FILE * f, const char * const prg)
{
  fprintf(f,
	  "\n"
	  "Usage: %s [--locale LOCALE]\n"
	  "\n"
	  "LOCALE may be one of:\n"
	  "  C       (English      American English)\n"
	  "  af_ZA   (Afrikaans)\n"
	  "  eu_ES   (Baque        Euskara)\n"
	  "  be_BY   (Belarusian   Bielaruskaja)\n"
	  "  nb_NO   (Bokmal)\n"
	  "  pt_BR   (Brazilian    Brazilian Portuguese   Portugues Brazilian)\n"
	  "  br_FR   (Breton       Brezhoneg)\n"
	  "  en_GB   (British      British English)\n"
          "  bg_BG   (Bulgarian)\n"
	  "  ca_ES   (Catalan      Catala)\n"
	  "  zh_CN   (Chinese-Simplified)\n"
	  "  zh_TW   (Chinese-Traditional)\n"
	  "  cs_CZ   (Czech        Cesky)\n"
	  "  da_DK   (Danish       Dansk)\n"
	  "  nl_NL   (Dutch)\n"
	  "  fi_FI   (Finnish      Suomi)\n"
	  "  fr_FR   (French       Francais)\n"
	  "  ga_IE   (Gaelic       Gaidhlig)\n"
	  "  gl_ES   (Galician     Galego)\n"
	  "  gos_NL  (Gronings     Zudelk Veenkelonioals)\n"
	  "  de_DE   (German       Deutsch)\n"
	  "  et_EE   (Estonian)\n"
	  "  el_GR   (Greek)\n"
	  "  he_IL   (Hebrew)\n"
	  "  hi_IN   (Hindi)\n"
	  "  hr_HR   (Croatian     Hrvatski)\n"
	  "  hu_HU   (Hungarian    Magyar)\n"
	  "  tlh     (Klingon      tlhIngan)\n"
	  "  is_IS   (Icelandic    Islenska)\n"
	  "  id_ID   (Indonesian   Bahasa Indonesia)\n"
	  "  it_IT   (Italian      Italiano)\n"
	  "  ja_JP   (Japanese)\n"
	  "  ka_GE   (Georgian)\n"
	  "  ko_KR   (Korean)\n"
	  "  ms_MY   (Malay)\n"
	  "  lt_LT   (Lithuanian   Lietuviu)\n"
	  "  nn_NO   (Norwegian    Nynorsk                Norsk)\n"
	  "  pl_PL   (Polish       Polski)\n"
	  "  pt_PT   (Portuguese   Portugues)\n"
	  "  ro_RO   (Romanian)\n"
	  "  ru_RU   (Russian      Russkiy)\n"
	  "  rw_RW   (Kinyarwanda)\n"
	  "  sk_SK   (Slovak)\n"
	  "  sl_SI   (Slovenian)\n"
	  "  sq_AL   (Albanian)\n"
	  "  sr_YU   (Serbian)\n"
	  "  es_ES   (Spanish      Espanol)\n"
	  "  es_MX   (Mexican      Mexican Spanish       Espanol Mejicano)\n"
	  "  sw_TZ   (Swahili)\n"
	  "  sv_SE   (Swedish      Svenska)\n"
	  "  th_TH   (Thai)\n"
	  "  tr_TR   (Turkish)\n"
	  "  uk_UA   (Ukrainian)\n"
	  "  vi_VN   (Vietnamese)\n"
	  "  wa_BE   (Walloon)\n"
	  "  cy_GB   (Welsh        Cymraeg)\n"
	  "\n",
	  prg);
}


typedef struct language_to_locale_struct {
  const char *language;
  const char *locale;
} language_to_locale_struct;

static const language_to_locale_struct language_to_locale_array[] = {
{"english",              "C"},
{"american-english",     "C"},
{"croatian",             "hr_HR.UTF-8"},
{"hrvatski",             "hr_HR.UTF-8"},
{"catalan",              "ca_ES.UTF-8"},
{"catala",               "ca_ES.UTF-8"},
{"belarusian",           "be_BY.UTF-8"},
{"bielaruskaja",         "be_BY.UTF-8"},
{"czech",                "cs_CZ.UTF-8"},
{"cesky",                "cs_CZ.UTF-8"},
{"danish",               "da_DK.UTF-8"},
{"dansk",                "da_DK.UTF-8"},
{"german",               "de_DE.UTF-8"},
{"deutsch",              "de_DE.UTF-8"},
{"estonian",             "et_EE.UTF-8"},
{"greek",                "el_GR.UTF-8"},
{"gronings",		 "gos_NL.UTF-8"},
{"zudelk-veenkelonioals","gos_NL.UTF-8"},
{"british-english",      "en_GB.UTF-8"},
{"british",              "en_GB.UTF-8"},
{"spanish",              "es_ES.UTF-8"},
{"mexican",              "es_MX.UTF-8"},
{"mexican-spanish",      "es_MX.UTF-8"},
{"espanol-mejicano",     "es_MX.UTF-8"},
{"espanol",              "es_ES.UTF-8"},
{"finnish",              "fi_FI.UTF-8"},
{"suomi",                "fi_FI.UTF-8"},
{"french",               "fr_FR.UTF-8"},
{"francais",             "fr_FR.UTF-8"},
{"gaelic",               "ga_IE.UTF-8"},
{"gaidhlig",             "ga_IE.UTF-8"},
{"galician",             "gl_ES.UTF-8"},
{"galego",               "gl_ES.UTF-8"},
{"hebrew",               "he_IL.UTF-8"},
{"hindi",                "hi_IN.UTF-8"},
{"hungarian",            "hu_HU.UTF-8"},
{"magyar",               "hu_HU.UTF-8"},
{"indonesian",           "id_ID.UTF-8"},
{"bahasa-indonesia",     "id_ID.UTF-8"},
{"icelandic",            "is_IS.UTF-8"},
{"islenska",             "is_IS.UTF-8"},
{"italian",              "it_IT.UTF-8"},
{"italiano",             "it_IT.UTF-8"},
{"japanese",             "ja_JP.UTF-8"},
{"vietnamese",           "vi_VN.UTF-8"},
{"afrikaans",            "af_ZA.UTF-8"},
{"albanian",             "sq_AL.UTF-8"},
{"breton",               "br_FR.UTF-8"},
{"brezhoneg",            "br_FR.UTF-8"},
{"bulgarian",            "bg_BG.UTF-8"},
{"welsh",                "cy_GB.UTF-8"},
{"cymraeg",              "cy_GB.UTF-8"},
{"bokmal",               "nb_NO.UTF-8"},
{"basque",               "eu_ES.UTF-8"},
{"euskara",              "eu_ES.UTF-8"},
{"georgian",             "ka_GE"},
{"korean",               "ko_KR.UTF-8"},
{"kinyarwanda",		 "rw_RW.UTF-8"},
{"klingon",              "tlh.UTF-8"},
{"tlhIngan",             "tlh.UTF-8"},
{"tlhingan",             "tlh.UTF-8"},
{"tamil",                "ta_IN.UTF-8"},
{"lithuanian",           "lt_LT.UTF-8"},
{"lietuviu",             "lt_LT.UTF-8"},
{"malay",                "ms_MY.UTF-8"},
{"dutch",                "nl_NL.UTF-8"},
{"nederlands",           "nl_NL.UTF-8"},
{"norwegian",            "nn_NO.UTF-8"},
{"nynorsk",              "nn_NO.UTF-8"},
{"norsk",                "nn_NO.UTF-8"},
{"polish",               "pl_PL.UTF-8"},
{"polski",               "pl_PL.UTF-8"},
{"brazilian-portuguese", "pt_BR.UTF-8"},
{"portugues-brazilian",  "pt_BR.UTF-8"},
{"brazilian",            "pt_BR.UTF-8"},
{"portuguese",           "pt_PT.UTF-8"},
{"portugues",            "pt_PT.UTF-8"},
{"romanian",             "ro_RO.UTF-8"},
{"russian",              "ru_RU.UTF-8"},
{"russkiy",              "ru_RU.UTF-8"},
{"slovak",               "sk_SK.UTF-8"},
{"slovenian",            "sl_SI.UTF-8"},
{"slovensko",            "sl_SI.UTF-8"},
{"serbian",              "sr_YU.UTF-8"},
{"swedish",              "sv_SE.UTF-8"},
{"svenska",              "sv_SE.UTF-8"},
{"swahili",              "sw_TZ.UTF-8"},
{"thai",                 "th_TH.UTF-8"},
{"turkish",              "tr_TR.UTF-8"},
{"ukrainian",            "uk_UA.UTF-8"},
{"walloon",              "wa_BE.UTF-8"},
{"walon",                "wa_BE.UTF-8"},
{"chinese",              "zh_CN.UTF-8"},
{"simplified-chinese",   "zh_CN.UTF-8"},
{"traditional-chinese",  "zh_TW.UTF-8"},
};


static void setup_language(const char * const prg)
{

// Justify this or delete it. It seems to make Tux Paint
// violate the behavior documented by "man 7 locale".
#if 0
  if (langstr == NULL && getenv("LANG"))
    {
      if(!strncasecmp(getenv("LANG"), "lt_LT", 5))
        set_langstr("lithuanian");
      if(!strncasecmp(getenv("LANG"), "pl_PL", 5))
        set_langstr("polish");
    }
#endif

  if (langstr != NULL)
    {
      int i = sizeof language_to_locale_array / sizeof language_to_locale_array[0];
      const char *locale = NULL;

      while(i--)
        {
          if (strcmp(langstr, language_to_locale_array[i].language))
            continue;
          locale = language_to_locale_array[i].locale;
          break;
        }

      if (!locale)
        {
          if (strcmp(langstr, "help") == 0 || strcmp(langstr, "list") == 0)
            {
              show_lang_usage(stdout, prg);
              //free(langstr);  // pointless
              exit(0);
            }
          else
            {
              fprintf(stderr, "%s is an invalid language\n", langstr);
              show_lang_usage(stderr, prg);
              //free(langstr);  // pointless
              exit(1);
            }
        }

        {
          char *s;
          ssize_t len;
          
	  len = strlen("LANGUAGE=")+strlen(locale)+1;
          s = malloc(len);
          snprintf(s, len, "LANGUAGE=%s", locale);
          putenv(s);
	  
          len = strlen("LC_ALL=")+strlen(locale)+1;
          s = malloc(len);
          snprintf(s, len, "LC_ALL=%s", locale);
          putenv(s);
        }
    
      setlocale(LC_ALL, "");
      free(langstr);
    }
 
  set_current_language();
}


// handle --locale arg
static void do_locale_option(const char * const arg)
{
  int len = strlen(arg) + 6;
  char *str = malloc(len);
  snprintf(str, len, "LANG=%s", arg);
  putenv(str);
  // We leak "str" because it can not be freed. It is now part
  // of the environment. If it were local, the environment would
  // get corrupted.
  setlocale(LC_ALL, ""); /* use arg ? */
}


static TTF_Font *try_alternate_font(int size)
{
  char  str[128];
  char  prefix[64];
  char  *p;

  strcpy(prefix, lang_prefix);
  if ((p = strrchr(prefix, '_')) != NULL)
  {
    *p = 0;
    snprintf(str, sizeof(str), "%sfonts/locale/%s.ttf",
             DATA_PREFIX, prefix);

    return TTF_OpenFont(str, size);
  }
  return NULL;
}


static TTF_Font *load_locale_font(TTF_Font *fallback, int size)
{
  TTF_Font *ret = NULL;
  if (need_own_font)
    {
      char str[128];
      snprintf(str, sizeof(str), "%sfonts/locale/%s.ttf",
	       DATA_PREFIX, lang_prefix);

      ret = TTF_OpenFont(str, size);

      if (ret == NULL)
      {
          ret = try_alternate_font(size);
          if (ret == NULL)
	  {
	      fprintf(stderr,
		      "\nWarning: Can't load font for this locale:\n"
		      "%s\n"
		      "The Simple DirectMedia Layer error that occurred was:\n"
		      "%s\n\n"
		      "Will use default (American English) instead.\n\n",
		      str, SDL_GetError());


	      /* Revert to default: */
      
	      putenv((char *) "LANG=C");
	      putenv((char *) "OUTPUT_CHARSET=C");
	      setlocale(LC_ALL, "C");

	      set_current_language();
	  }
      }
    }
  return ret ? ret : fallback;
}

///////////////////////////////////////////////////////////////////
// sizing

// The old Tux Paint:
// 640x480 screen
// 448x376 canvas
//  40x96  titles near the top
//  48x48  button tiles
//  ??x56  tux area
//  room for 2x7 button tile grids

typedef struct {
  Uint8 rows, cols;
} grid_dims;

//static SDL_Rect r_screen; // was 640x480 @ 0,0  -- but this isn't so useful
static SDL_Rect r_canvas; // was 448x376 @ 96,0
static SDL_Rect r_tools; // was 96x336 @ 0,40
static SDL_Rect r_toolopt; // was 96x336 @ 544,40
static SDL_Rect r_colors; // was 544x48 @ 96,376
static SDL_Rect r_ttools; // was 96x40 @ 0,0  (title for tools, "Tools")
static SDL_Rect r_tcolors; // was 96x48 @ 0,376 (title for colors, "Colors")
static SDL_Rect r_ttoolopt; // was 96x40 @ 544,0 (title for tool options)
static SDL_Rect r_tuxarea; // was 640x56

static int button_w;  // was 48
static int button_h;  // was 48

static int color_button_w;  // was 32
static int color_button_h;  // was 48

// Define button grid dimensions. (in button units)
// These are the maximum slots -- some may be unused.
static grid_dims gd_tools;   // was 2x7
static grid_dims gd_toolopt; // was 2x7
//static grid_dims gd_open;    // was 4x4
static grid_dims gd_colors;   // was 17x1

#define HEIGHTOFFSET (((WINDOW_HEIGHT - 480) / 48) * 48)
#define TOOLOFFSET (HEIGHTOFFSET / 48 * 2)
#define PROMPTOFFSETX (WINDOW_WIDTH - 640) / 2
#define PROMPTOFFSETY (HEIGHTOFFSET / 2)

#define THUMB_W ((WINDOW_WIDTH - 96 - 96) / 4)
#define THUMB_H (((48 * 7 + 40 + HEIGHTOFFSET) - 72) / 4)

static int WINDOW_WIDTH, WINDOW_HEIGHT;


static void setup_normal_screen_layout(void)
{
  int buttons_tall;

  button_w = 48;
  button_h = 48;

  gd_toolopt.cols = 2;
  gd_tools.cols = 2;

  r_ttools.x = 0;
  r_ttools.y = 0;
  r_ttools.w = gd_tools.cols * button_w;
  r_ttools.h = 40;

  r_ttoolopt.w = gd_toolopt.cols * button_w;
  r_ttoolopt.h = 40;
  r_ttoolopt.x = WINDOW_WIDTH - r_ttoolopt.w;
  r_ttoolopt.y = 0;

  gd_colors.rows = 1;
  gd_colors.cols = (NUM_COLORS + gd_colors.rows - 1) / gd_colors.rows;

  r_colors.h = 48;
  color_button_h = r_colors.h / gd_colors.rows;
  r_tcolors.h = r_colors.h;

  r_tcolors.x = 0;
  r_tcolors.w = gd_tools.cols * button_w;
  r_colors.x = r_tcolors.w;
  r_colors.w = WINDOW_WIDTH - r_tcolors.w;

  color_button_w = r_colors.w / gd_colors.cols;

  // This would make it contain _just_ the color spots,
  // without any leftover bit on the end. Hmmm...
  // r_colors.w = color_button_w * gd_colors.cols;

  r_canvas.x = gd_tools.cols * button_w;
  r_canvas.y = 0;
  r_canvas.w = WINDOW_WIDTH - (gd_tools.cols+gd_toolopt.cols) * button_w;

  r_tuxarea.x = 0;
  r_tuxarea.w = WINDOW_WIDTH;

  // need 56 minimum for the Tux area
  buttons_tall = (WINDOW_HEIGHT -  r_ttoolopt.h - 56 - r_colors.h) / button_h;
  gd_tools.rows = buttons_tall;
  gd_toolopt.rows = buttons_tall;

  r_canvas.h = r_ttoolopt.h + buttons_tall * button_h;
  
  r_colors.y = r_canvas.h + r_canvas.y;
  r_tcolors.y = r_canvas.h + r_canvas.y;

  r_tuxarea.y = r_colors.y + r_colors.h;
  r_tuxarea.h = WINDOW_HEIGHT - r_tuxarea.y;

  r_tools.x = 0;
  r_tools.y = r_ttools.h + r_ttools.y;
  r_tools.w = gd_tools.cols * button_w;
  r_tools.h = gd_tools.rows * button_h;

  r_toolopt.w = gd_toolopt.cols * button_w;
  r_toolopt.h = gd_toolopt.rows * button_h;
  r_toolopt.x = WINDOW_WIDTH - r_ttoolopt.w;
  r_toolopt.y = r_ttoolopt.h + r_ttoolopt.y;

  // TODO: dialog boxes

}

#ifdef DEBUG
static void debug_rect(SDL_Rect *r, char *name)
{
  printf("%-12s %dx%d @ %d,%d\n", name, r->w, r->h, r->x, r->y);
}
#define DR(x) debug_rect(&x, #x)

static void debug_dims(grid_dims *g, char *name)
{
  printf("%-12s %dx%d\n", name, g->cols, g->rows);
}
#define DD(x) debug_dims(&x, #x)

static void print_layout(void)
{
  printf("\n--- layout ---\n");
  DR(r_canvas);
  DR(r_tools);
  DR(r_toolopt);
  DR(r_colors);
  DR(r_ttools);
  DR(r_tcolors);
  DR(r_ttoolopt);
  DR(r_tuxarea);
  DD(gd_tools);
  DD(gd_toolopt);
  DD(gd_colors);
  printf("buttons are %dx%d\n", button_w,button_h);
  printf("color buttons are %dx%d\n", color_button_w,color_button_h);
}
#undef DD
#undef DR
#endif

static void setup_screen_layout(void)
{
  // can do right-to-left, colors at the top, extra tool option columns, etc.
  setup_normal_screen_layout();
#ifdef DEBUG
  print_layout();
#endif
}

static SDL_Surface * screen;
static SDL_Surface * canvas;
static SDL_Surface * img_starter, * img_starter_bkgd;

/* Update a rect. based on two x/y coords (not necessarly in order): */
static void update_screen(int x1, int y1, int x2, int y2)
{
  int tmp;

  if (x1 > x2)
    {
      tmp = x1;
      x1 = x2;
      x2 = tmp;
    }

  if (y1 > y2)
    {
      tmp = y1;
      y1 = y2;
      y2 = tmp;
    }

  x1 = x1 - 1;
  x2 = x2 + 1;
  y1 = y1 - 1;
  y2 = y2 + 1;


  if (x1 < 0)
    x1 = 0;
  if (x2 < 0)
    x2 = 0;
  if (y1 < 0)
    y1 = 0;
  if (y2 < 0)
    y2 = 0;

  if (x1 >= WINDOW_WIDTH)
    x1 = WINDOW_WIDTH - 1;
  if (x2 >= WINDOW_WIDTH)
    x2 = WINDOW_WIDTH - 1;
  if (y1 >= WINDOW_HEIGHT)
    y1 = WINDOW_HEIGHT - 1;
  if (y2 >= WINDOW_HEIGHT)
    y2 = WINDOW_HEIGHT - 1;

  SDL_UpdateRect(screen, x1, y1, x2-x1+1, y2-y1+1);
}


static void update_screen_rect(SDL_Rect *r)
{
  SDL_UpdateRect(screen, r->x, r->y, r->w, r->h);
}

static int hit_test(const SDL_Rect * const r, unsigned x, unsigned y)
{
  // note the use of unsigned math: no need to check for negative
  return   x - r->x < r->w   &&   y - r->y < r->h  ;
}

#define HIT(r) hit_test(&(r), event.button.x, event.button.y)


/* "#if"ing out, since unused; bjk 2005.01.09 */

#if 0

// x,y are pixel-wise screen-relative (mouse location), not grid-wise
// w,h are the size of a grid item
// Return the grid box.
// NOTE: grid items must fill full SDL_Rect width exactly
static int grid_hit_wh(const SDL_Rect * const r, unsigned x, unsigned y, unsigned w, unsigned h)
{
  return   (x - r->x) / w  +  (y - r->y) / h * (r->w/w) ;
}

// test an SDL_Rect r containing an array of WxH items for a grid location
#define GRIDHIT_WH(r,W,H) grid_hit_wh(&(r), event.button.x, event.button.y, W,H)

#endif

// test an SDL_Rect r containing an array of SDL_Surface surf for a grid location
#define GRIDHIT_SURF(r,surf) grid_hit_wh(&(r), event.button.x, event.button.y, (surf)->w, (surf)->h)

// x,y are pixel-wise screen-relative (mouse location), not grid-wise
// Return the grid box.
// NOTE: returns -1 if hit is below or to the right of the grid
static int grid_hit_gd(const SDL_Rect * const r, unsigned x, unsigned y, grid_dims *gd)
{
  unsigned item_w = r->w / gd->cols;
  unsigned item_h = r->h / gd->rows;
  unsigned col = (x - r->x) / item_w;
  unsigned row = (y - r->y) / item_h;
#ifdef DEBUG
  printf("%d,%d resolves to %d,%d in a %dx%d grid, index is %d\n", x,y, col,row, gd->cols,gd->rows, col+row*gd->cols);
#endif
  if (col >= gd->cols || row >= gd->rows)
    return -1;
  return col + row * gd->cols;
}

// test an SDL_Rect r for a grid location, based on a grid_dims gd
#define GRIDHIT_GD(r,gd) grid_hit_gd(&(r), event.button.x, event.button.y, &(gd))

/* Update the screen with the new canvas: */
static void update_canvas(int x1, int y1, int x2, int y2)
{
  SDL_Rect src, dest;

  if (img_starter != NULL)
  {
    /* If there was a starter, cover this part of the drawing with
       the corresponding part of the starter's foreground! */

    src.x = x1;
    src.y = y1;
    src.w = x2 - x1 + 1;
    src.h = y2 - y1 + 1;

    dest.x = x1;
    dest.y = y1;
    dest.w = src.w;
    dest.h = src.h;

    SDL_BlitSurface(img_starter, &dest, canvas, &dest);
  }

  SDL_BlitSurface(canvas, NULL, screen, &r_canvas);
  update_screen(x1 + 96, y1, x2 + 96, y2);
}


///////////////////////////////////////////////////////////////////

/* Globals: */

static int use_sound, fullscreen, disable_quit, simple_shapes,
  disable_print, print_delay, only_uppercase, promptless_save, grab_input,
  wheely, no_fancy_cursors, keymouse, mouse_x, mouse_y,
  mousekey_up, mousekey_down, mousekey_left, mousekey_right,
  dont_do_xor, use_print_config, dont_load_stamps, noshortcuts,
  no_system_fonts, no_button_distinction,
  mirrorstamps, disable_stamp_controls, disable_save, ok_to_use_lockfile,
  alt_print_command_default, mute;
static int want_alt_printcommand;
static int starter_mirrored, starter_flipped;
static int recording, playing;
static char * playfile;
static FILE * demofi;
static const char * printcommand = PRINTCOMMAND;
static const char * altprintcommand = ALTPRINTCOMMAND;
static int prog_bar_ctr;

enum {
  UNDO_STARTER_NONE,
  UNDO_STARTER_MIRRORED,
  UNDO_STARTER_FLIPPED
};

#define NUM_UNDO_BUFS 20
static SDL_Surface * undo_bufs[NUM_UNDO_BUFS];
static int undo_starters[NUM_UNDO_BUFS];
static int cur_undo, oldest_undo, newest_undo;

static SDL_Surface * img_title, * img_title_credits, * img_title_tuxpaint;
static SDL_Surface * img_progress;
static SDL_Surface * img_btn_up, * img_btn_down, * img_btn_off;
static SDL_Surface * img_dead40x40;
static SDL_Surface * img_black, * img_grey;
static SDL_Surface * img_yes, * img_no;
static SDL_Surface * img_open, * img_erase, * img_back, * img_trash;
static SDL_Surface * img_printer, * img_printer_wait, * img_save_over;
static SDL_Surface * img_popup_arrow;
static SDL_Surface * img_cursor_up, * img_cursor_down;
static SDL_Surface * img_cursor_starter_up, * img_cursor_starter_down;
static SDL_Surface * img_scroll_up, * img_scroll_down;
static SDL_Surface * img_scroll_up_off, * img_scroll_down_off;
static SDL_Surface * img_grow, * img_shrink;
static SDL_Surface * img_bold, * img_italic;

static SDL_Surface * img_sparkles;
static SDL_Surface * img_grass;

static SDL_Surface * img_title_on, * img_title_off,
  * img_title_large_on, * img_title_large_off;
static SDL_Surface * img_title_names[NUM_TITLES];
static SDL_Surface * img_tools[NUM_TOOLS], * img_tool_names[NUM_TOOLS];

static SDL_Surface * thumbnail(SDL_Surface * src, int max_x, int max_y,
			int keep_aspect);

//////////////////////////////////////////////////////////////////////
// font stuff

// example from a Debian box with MS fonts:
// start with 232 files
// remove "Cursor", "Webdings", "Dingbats", "Standard Symbols L"
// split "Condensed" faces out into own family
// group by family
// end up with 34 user choices

static int text_state;
// nice progression (alternating 33% and 25%) 9 12 18 24 36 48 72 96 144 192
// commonly hinted sizes seem to be: 9, 10, 12, 14, 18, 20 (less so), 24
// reasonable: 9,12,18... and 10,14,18...
static int text_sizes[] = {9, 12, 18, 24, 36, 48,
	                   56, 64, 96, 112, 128, 160};  // point sizes
#define MIN_TEXT_SIZE 0u
#define MAX_TEXT_SIZE (sizeof text_sizes / sizeof text_sizes[0] - 1)
static unsigned text_size = 4;   // initial text size


// for sorting through the font files at startup
typedef struct style_info {
  char *filename;
  char *directory;
  char *family;    // name like "FooCorp Thunderstruck"
  char *style;     // junk like "Oblique Demi-Bold"
  int italic;
  int boldness;
  int score;
  int truetype; // Is it? (TrueType gets priority)
} style_info;

// user's notion of a font
typedef struct family_info {
  char *directory;
  char *family;
  char *filename[4];
  TTF_Font *handle;
  int score;
} family_info;

static TTF_Font * medium_font, * small_font, * large_font, * locale_font;

static family_info **user_font_families;
static int num_font_families = 0;
static int num_font_families_max = 0;

static style_info **user_font_styles;
static int num_font_styles = 0;
static int num_font_styles_max = 0;

static void receive_some_font_info(void);

static TTF_Font *getfonthandle(int desire)
{
  int missing = 0;
  family_info *fi = user_font_families[desire];
  char *name = fi->filename[text_state];
  char *pathname;

  if(fi->handle)
    return fi->handle;

  if(!name)
    {
      name = fi->filename[text_state ^ TTF_STYLE_ITALIC];
      missing = text_state & TTF_STYLE_ITALIC;
    }
  if(!name)
    {
      name = fi->filename[text_state ^ TTF_STYLE_BOLD];
      missing = text_state & TTF_STYLE_BOLD;
    }
  if(!name)
    {
      name = fi->filename[text_state ^ (TTF_STYLE_ITALIC|TTF_STYLE_BOLD)];
      missing = text_state & (TTF_STYLE_ITALIC|TTF_STYLE_BOLD);
    }
  pathname = alloca(strlen(fi->directory) + 1 + strlen(name) + 1);
  sprintf(pathname, "%s/%s", fi->directory, name);
  
  fi->handle = TTF_OpenFont(pathname,text_sizes[text_size]);
  // if the font doesn't load, we die -- it did load OK before though
  TTF_SetFontStyle(fi->handle,missing);
  return fi->handle;
}


static SDL_Surface *render_text(TTF_Font *restrict font, const char *restrict str, SDL_Color color)
{
  SDL_Surface *ret;
  int height;

  ret = TTF_RenderUTF8_Blended(font, str, color);
  if(ret)
    return ret;
  // Sometimes a font will be missing a character we need. Sometimes the library
  // will substitute a rectangle without telling us. Sometimes it returns NULL.
  // Probably we should use FreeType directly. For now though...
  height = TTF_FontHeight(font);
  if(height<2)
    height = 2;
  return thumbnail(img_title_large_off, height*strlen(str)/2, height, 0);
}


// void qsort(void *base, size_t nmemb, size_t size,
// int(*compar)(const void *, const void *));


// For qsort() and other use, to see if font files are groupable
static int compar_fontgroup(const void *v1, const void *v2)
{
  const style_info *s1 = *(style_info**)v1;
  const style_info *s2 = *(style_info**)v2;
  int rc;

  rc = strcmp(s1->directory,s2->directory);
  if(rc)
    return rc;

  rc = s1->truetype - s2->truetype;
  if(rc)
    return rc;

  return strcmp(s1->family,s2->family);
}


// For qsort() and other use, to see if font files are duplicates
static int compar_fontkiller(const void *v1, const void *v2)
{
  const family_info *f1 = *(family_info**)v1;
  const family_info *f2 = *(family_info**)v2;
  int rc;

  rc = strcmp(f1->family,f2->family);
  if(rc)
    return rc;

  return f1->score - f2->score;
}


// For qsort() and other use, to order the worst ones last
static int compar_fontscore(const void *v1, const void *v2)
{
  const family_info *f1 = *(family_info**)v1;
  const family_info *f2 = *(family_info**)v2;

  return f2->score - f1->score;
}

// Font style names are a mess that we must try to make
// sense of. For example...
//
// Cooper: Light, Medium, Light Bold, Black
// HoeflerText: (nil), Black
static void parse_font_style(style_info *si)
{
  int have_light = 0;
  int have_demi = 0;
  int have_bold = 0;
  int have_medium = 0;
  int have_black = 0;
  int have_heavy = 0;

  int stumped = 0;
  char *sp = si->style;

  si->italic = 0;


  while(*sp)
    {
      if(*sp==' ')
        {
          sp++;
          continue;
        }
      if(!strncasecmp(sp,"Bold",strlen("Bold")))
        {
          sp += strlen("Bold");
          have_bold = 1;
          continue;
        }
      if(!strncasecmp(sp,"Regular",strlen("Regular")))
        {
          sp += strlen("Regular");
          continue;
        }
      if(!strncasecmp(sp,"Italic",strlen("Italic")))
        {
          sp += strlen("Italic");
          si->italic = 1;
          continue;
        }
      if(!strncasecmp(sp,"Oblique",strlen("Oblique")))
        {
          sp += strlen("Oblique");
          si->italic = 1;
          continue;
        }
      // move " Condensed" from style to family
      if(!strncasecmp(sp,"Condensed",strlen("Condensed")))
        {
          size_t len = strlen(si->family);
          char *name = malloc(len + strlen(" Condensed") + 1);
          sp += strlen("Condensed");
          memcpy(name,si->family,len);
          strcpy(name+len," Condensed");
          free(si->family);
          si->family = name;
          continue;
        }
      if(!strncasecmp(sp,"Light",strlen("Light")))
        {
          sp += strlen("Light");
          have_light = 1;
          continue;
        }
      if(!strncasecmp(sp,"Medium",strlen("Medium")))
        {
          sp += strlen("Medium");
          have_medium = 1;
          continue;
        }
      if(!strncasecmp(sp,"Demi",strlen("Demi")))
        {
          sp += strlen("Demi");
          have_demi = 1;
          continue;
        }
      if(!strncasecmp(sp,"Heavy",strlen("Heavy")))
        {
          sp += strlen("Heavy");
          have_heavy = 1;
          continue;
        }
      if(!strncasecmp(sp,"Normal",strlen("Normal")))
        {
          sp += strlen("Normal");
          continue;
        }
      if(!strncasecmp(sp,"Black",strlen("Black")))
        {
          sp += strlen("Black");
          have_black = 1;
          continue;
        }
      if(!strncasecmp(sp,"Roman",strlen("Roman")))
        {
          sp += strlen("Roman");
          continue;
        }
      if(!strncasecmp(sp,"Book",strlen("Book")))
        {
          sp += strlen("Book");
          continue;
        }
      if(!strncasecmp(sp,"Chancery",strlen("Chancery")))
        {
          sp += strlen("Chancery");
          si->italic = 1;
          continue;
        }
      if(!strncasecmp(sp,"Thin",strlen("Thin")))
        {
          sp += strlen("Thin");
          have_light = 1;
          continue;
        }
      if(!strncmp(sp,"LR",strlen("LR")))
        {
          sp += strlen("LR");
          continue;
        }

      if(!stumped)
        {
          stumped=1;
#if 0
// THREADED_FONTS
          printf("Font style parser stumped by \"%s\".\n", si->style);
#endif
        }
      sp++; // bad: an unknown character
    }

  if (have_demi || have_medium)
    si->boldness = 2;
  else if (have_bold || have_black || have_heavy) // TODO: split these
    si->boldness = 3;
  else if (have_light)
    si->boldness = 0;
  else
    si->boldness = 1;

  // we'll count both TrueType and OpenType
  si->truetype = !!strcasestr(si->filename,".ttf") || !!strcasestr(si->filename,".otf");
}

static void groupfonts_range(style_info **base, int count)
{
  int boldcounts[4] = {0,0,0,0};
  int boldmap[4] = {-1,-1,-1,-1};
  int i;
  int boldmax;
  int boldmin;
  int bolduse;
  int spot;
  family_info *fi;

#if 0
// THREADED_FONTS
if(count<1 || count>4)
{
printf("\n::::::: %d styles in %s:\n",count, base[0]->family);
i = count;
while(i--)
{
printf("               %s\n", base[i]->style);
}
}
#endif

  i = count;
  while(i--)
    boldcounts[base[i]->boldness]++;

  boldmax = base[0]->boldness;
  boldmin = base[0]->boldness;
  bolduse = 0;

  i = 4;
  while(i--)
    {
      if(!boldcounts[i])
        continue;
      if(i>boldmax)
        boldmax = i;
      if(i<boldmin)
        boldmin = i;
      bolduse++;
    }
  if(likely(bolduse<=2))
    {
      // in case they are same, we want non-bold,
      // so that setting goes second
      boldmap[boldmax] = 1;
      boldmap[boldmin] = 0;
    }
  else if(count==3)
    {
      int boldmid;
      int zmin = 0, zmid = 0, zmax = 0;
      
      boldmap[boldmax] = 1;
      boldmap[boldmin] = 0;
      boldmid = boldcounts[boldmin+1] ? boldmin+1 : boldmin+2;

      i = 3;
      while(i--){
        if(base[i]->boldness==boldmin)
          zmin = base[i]->italic;
        if(base[i]->boldness==boldmid)
          zmid = base[i]->italic;
        if(base[i]->boldness==boldmax)
          zmax = base[i]->italic;
      }
      if(zmin!=zmid)
        boldmap[boldmid] = 0;
      else if(zmid!=zmax)
        boldmap[boldmid] = 1;
      else if(boldmin==0 && boldmid==1)
        {
          boldmap[0] = -1;
          boldmap[1] = 0;
        }
    }
  else
    {
      int claimed_bold = boldcounts[3];
      int claimed_norm = boldcounts[1];

      // 3 or 4 boldness levels, 4 or more styles!
      // This is going to be random hacks and hopes.

      // bold is bold
      boldmap[3] = 1;

      // norm is norm
      boldmap[1] = 0;

      // classify demi-bold or medium
      if(claimed_bold<2)
        {
          boldmap[2] = 1;
          claimed_bold += boldcounts[2];
        }
      else if(claimed_norm<2)
        {
          boldmap[2] = 0;
          claimed_norm += boldcounts[2];
        }

      // classify lightface
      if(claimed_norm<2)
        {
          boldmap[0] = 0;
          //claimed_norm += boldcounts[0];
        }
    }

  if (num_font_families==num_font_families_max)
    {
      num_font_families_max = num_font_families_max * 5 / 4 + 30;
      user_font_families = realloc(user_font_families, num_font_families_max * sizeof *user_font_families); 
    }

  fi = calloc(1, sizeof *fi);
  user_font_families[num_font_families++] = fi;
  fi->directory = strdup(base[0]->directory);
  fi->family    = strdup(base[0]->family);
  fi->score     = base[0]->truetype + base[0]->score;
  i = count;
  while(i--)
    {
      int b = boldmap[base[i]->boldness];
      if(b==-1)
        {
#if 0
// THREADED_FONTS
          printf("too many boldness levels, discarding: %s, %s\n", base[i]->family, base[i]->style);
#endif
          continue;
        }
      spot = b ? TTF_STYLE_BOLD : 0;
      spot += base[i]->italic ? TTF_STYLE_ITALIC : 0;
      if(fi->filename[spot])
        {
#if 0
// THREADED_FONTS
          printf("duplicates, discarding: %s, %s\n", base[i]->family, base[i]->style);
          printf("b %d, spot %d\n", b, spot);
          printf("occupancy %p %p %p %p\n", fi->filename[0], fi->filename[1], fi->filename[2], fi->filename[3]);
#endif
          continue;
        }
      fi->filename[spot] = strdup(base[i]->filename);
      fi->score += 2;
    }
  if(!fi->filename[0] && !fi->filename[1])
    {
      fi->filename[0] = fi->filename[2];
      fi->filename[2] = NULL;
      fi->filename[1] = fi->filename[3];
      fi->filename[3] = NULL;
    }
  if(!fi->filename[0] && !fi->filename[2])
    {
      fi->filename[0] = fi->filename[1];
      fi->filename[1] = NULL;
      fi->filename[2] = fi->filename[3];
      fi->filename[3] = NULL;
    }
  if(!fi->filename[0])
    {
      fi->filename[0] = strdup(fi->filename[TTF_STYLE_BOLD]);
    }
}


static void dupe_markdown_range(family_info **base, int count)
{
  int bestscore = -999;
  int bestslot = 0;
  int i = count;
  while(i--)
    {
      int score = base[i]->score;
      if(score<=bestscore)
        continue;
      bestscore = score;
      bestslot = i;
    }
  i = count;
  while(i--)
    {
      if (i==bestslot)
        continue;
      base[i]->score = -999;
    }
}


static void groupfonts(void)
{
  char * * cpp;
  int i = num_font_styles;
  int low  = 0;

  while(i--)
    parse_font_style(user_font_styles[i]);

  qsort(user_font_styles, num_font_styles, sizeof user_font_styles[0], compar_fontgroup);
  //printf("groupfonts() qsort(user_font_styles...)\n");
  //fflush(stdout);

  for(;;)
    {
      int high = low;
      if(low >= num_font_styles)
        break;
      for(;;)
        {
          if(++high >= num_font_styles)
            break;
          if(compar_fontgroup(user_font_styles+low, user_font_styles+high))
            break;
        }
      groupfonts_range(user_font_styles+low, high-low);
      low = high;
    }

  i = num_font_styles;
  while(i--)
    {
      free(user_font_styles[i]->filename);
      free(user_font_styles[i]->directory);
      free(user_font_styles[i]->family);
      free(user_font_styles[i]->style);
      free(user_font_styles[i]);
    }
  free(user_font_styles);
  user_font_styles = NULL; // just to catch bugs

  qsort(user_font_families, num_font_families, sizeof user_font_families[0], compar_fontkiller);
  //printf(stderr, "groupfonts() qsort(user_font_families 1...)\n");
  //fflush(stdout);
  low = 0;
  for(;;)
    {
      int high = low;
      if(low >= num_font_families)
        break;
      for(;;)
        {
          if(++high >= num_font_families)
            break;
          if(strcmp(user_font_families[low]->family,user_font_families[high]->family))
            break;
        }
      dupe_markdown_range(user_font_families+low,high-low);
      low = high;
    }
  qsort(user_font_families, num_font_families, sizeof user_font_families[0], compar_fontscore);
  //printf("groupfonts() qsort(user_font_families 2...)\n");
  //fflush(stdout);
  if(user_font_families[0]->score < 0)
    printf("sorted the wrong way, or all fonts were unusable\n");
#if 0
// THREADED_FONTS
  printf("Trim starting with %d families\n", num_font_families);
#endif
  while(num_font_families>1 && user_font_families[num_font_families-1]->score < 0)
    {
      i = --num_font_families;
      free(user_font_families[i]->directory);
      free(user_font_families[i]->family);
      cpp = user_font_families[i]->filename;
      if(cpp[0])
        free(cpp[0]);
      if(cpp[1])
        free(cpp[1]);
      if(cpp[2])
        free(cpp[2]);
      if(cpp[3])
        free(cpp[3]);
      free(user_font_families[i]);
      user_font_families[i] = NULL;
    }
#if 0
// THREADED_FONTS
  printf("Trim ending with %d families\n", num_font_families);
#endif
}

////////////////////////////////////////////////////////////////////

typedef struct stamp_type {
  char *stampname;
  char *stxt;
#ifndef NOSOUND
  Mix_Chunk *ssnd;
#endif

  SDL_Surface *thumbnail;
  unsigned thumb_mirrored : 1;
  unsigned thumb_flipped : 1;
  unsigned no_premirror : 1;

  unsigned processed : 1;  // got *.dat, computed size limits, etc.

  unsigned no_sound : 1;
  unsigned no_txt : 1;
//  unsigned no_local_sound : 1;  // to remember, if code written to discard sound

  unsigned tinter : 3;
  unsigned colorable : 1;
  unsigned tintable : 1;
  unsigned mirrorable : 1;
  unsigned flipable : 1;

  unsigned mirrored : 1;
  unsigned flipped : 1;
  unsigned min : 5;
  unsigned size : 5;
  unsigned max : 5;
} stamp_type;

static int num_stamps;
static int max_stamps;
static stamp_type **stamp_data;

static SDL_Surface *active_stamp;

/* Returns whether a particular stamp can be colored: */
static int stamp_colorable(int stamp)
{
  return stamp_data[stamp]->colorable;
}

/* Returns whether a particular stamp can be tinted: */
static int stamp_tintable(int stamp)
{
  return stamp_data[stamp]->tintable;
}



static int num_brushes, num_brushes_max;
static SDL_Surface **img_brushes;

static SDL_Surface * img_shapes[NUM_SHAPES], * img_shape_names[NUM_SHAPES];
static SDL_Surface * img_magics[NUM_MAGICS], * img_magic_names[NUM_MAGICS];
static SDL_Surface * img_openlabels_open, * img_openlabels_erase,
  * img_openlabels_back;

static SDL_Surface * img_tux[NUM_TIP_TUX];

static SDL_Surface * img_mouse, * img_mouse_click;

#ifdef LOW_QUALITY_COLOR_SELECTOR
static SDL_Surface * img_paintcan;
#else
static SDL_Surface * img_color_btns[NUM_COLORS*2];
static SDL_Surface * img_color_btn_off;
#endif

static int colors_are_selectable;

static SDL_Surface * img_cur_brush;
static int brush_counter, rainbow_color;

#ifndef NOSOUND
static Mix_Chunk * sounds[NUM_SOUNDS];
#endif

#define NUM_ERASERS 6 /* How many sizes of erasers (from ERASER_MIN to _MAX) */
#define ERASER_MIN 13 
#define ERASER_MAX 128 


static SDL_Cursor * cursor_hand, * cursor_arrow, * cursor_watch,
  * cursor_up, * cursor_down, * cursor_tiny, * cursor_crosshair,
  * cursor_brush, * cursor_wand, * cursor_insertion, * cursor_rotate;


static unsigned cur_color;
static int cur_tool, cur_brush, cur_stamp, cur_shape, cur_magic;
static int cur_font, cur_eraser;
static int cursor_left, cursor_x, cursor_y, cursor_textwidth; // canvas-relative
static int been_saved;
static char file_id[32];
static char starter_id[32];
static int brush_scroll, stamp_scroll, font_scroll, magic_scroll;
static int eraser_scroll, shape_scroll; // dummy variables for now

static int eraser_sound;

static char texttool_str[256];
static unsigned int texttool_len;

static int tool_avail[NUM_TOOLS], tool_avail_bak[NUM_TOOLS];

typedef struct edge_type {
  int y_upper;
  float x_intersect, dx_per_scan;
  struct edge_type * next;
} edge;


typedef struct point_type {
  int x, y;
} point_type;

typedef struct fpoint_type {
  float x, y;
} fpoint_type;

typedef enum { Left, Right, Bottom, Top } an_edge;
#define NUM_EDGES 4

static SDL_Event scrolltimer_event;

int non_left_click_count = 0;


static char * savedir;

typedef struct dirent2 {
  struct dirent f;
  int place;
} dirent2;


/* Local function prototypes: */

static void mainloop(void);
static void brush_draw(int x1, int y1, int x2, int y2, int update);
static void blit_brush(int x, int y);
static void magic_draw(int x1, int y1, int x2, int y2, int button_down);
static void blit_magic(int x, int y, int button_down);
static void stamp_draw(int x, int y);
static void rec_undo_buffer(void);
static void show_usage(FILE * f, char * prg);
static void setup(int argc, char * argv[]);
void signal_handler(int sig);
static SDL_Cursor * get_cursor(unsigned char * bits, unsigned char * mask_bits,
			       unsigned int w, unsigned int h,
			       unsigned int x, unsigned int y);
static void seticon(void);
static SDL_Surface * loadimage(const char * const fname);
static SDL_Surface * do_loadimage(const char * const fname, int abort_on_error);
static void draw_toolbar(void);
static void draw_magic(void);
static void draw_brushes(void);
static void draw_stamps(void);
static void draw_shapes(void);
static void draw_erasers(void);
static void draw_fonts(void);
static void draw_none(void);

static void putpixel8(SDL_Surface * surface, int x, int y, Uint32 pixel);
static void putpixel16(SDL_Surface * surface, int x, int y, Uint32 pixel);
static void putpixel24(SDL_Surface * surface, int x, int y, Uint32 pixel);
static void putpixel32(SDL_Surface * surface, int x, int y, Uint32 pixel);

static void (*putpixels[])(SDL_Surface *, int, int, Uint32) = {
  putpixel8, putpixel8, putpixel16, putpixel24, putpixel32 };

static Uint32 getpixel8(SDL_Surface * surface, int x, int y);
static Uint32 getpixel16(SDL_Surface * surface, int x, int y);
static Uint32 getpixel24(SDL_Surface * surface, int x, int y);
static Uint32 getpixel32(SDL_Surface * surface, int x, int y);

static Uint32 (*getpixels[])(SDL_Surface *, int, int) = {
  getpixel8, getpixel8, getpixel16, getpixel24, getpixel32 };

static void do_undo(void);
static void do_redo(void);
static void render_brush(void);
static void playsound(int chan, int s, int override);
static void line_xor(int x1, int y1, int x2, int y2);
static void rect_xor(int x1, int y1, int x2, int y2);

#ifdef LOW_QUALITY_STAMP_OUTLINE
#define stamp_xor(x,y) rect_xor( \
  (x) - (CUR_STAMP_W+1)/2, \
  (y) - (CUR_STAMP_H+1)/2, \
  (x) + (CUR_STAMP_W+1)/2, \
  (y) + (CUR_STAMP_H+1)/2 \
)
#define update_stamp_xor()
#else
static void stamp_xor(int x1, int y1);
static void update_stamp_xor(void);
#endif

static void set_active_stamp(void);

static void do_eraser(int x, int y);
static void disable_avail_tools(void);
static void enable_avail_tools(void);
static void reset_avail_tools(void);
static int compare_dirent2s(struct dirent2 * f1, struct dirent2 * f2);
static void draw_tux_text(int which_tux, const char * const str,
		          int want_right_to_left);
static void wordwrap_text(const char * const str, SDL_Color color,
		   int left, int top, int right,
		   int want_right_to_left);
static char * loaddesc(const char * const fname);
static double loadinfo(const char * const fname, stamp_type *inf);
#ifndef NOSOUND
static Mix_Chunk * loadsound(const char * const fname);
#endif
static void do_wait(int counter);
static void load_current(void);
static void save_current(void);
static char * get_fname(const char * const name);
static int do_prompt_image_flash(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3, int animate);
static int do_prompt_image_flash_snd(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3, int animate, int snd);
static int do_prompt_image(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3);
static int do_prompt_image_snd(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3, int snd);
static int do_prompt(const char * const text, const char * const btn_yes, const char * const btn_no);
static int do_prompt_snd(const char * const text, const char * const btn_yes, const char * const btn_no, int snd);
static void cleanup(void);
static void free_cursor(SDL_Cursor ** cursor);
static void free_surface(SDL_Surface **surface_array);
static void free_surface_array(SDL_Surface *surface_array[], int count);
//static void update_shape(int cx, int ox1, int ox2, int cy, int oy1, int oy2,
//		  int fixed);
static void do_shape(int cx, int cy, int ox, int oy, int rotn, int use_brush);
static int rotation(int ctr_x, int ctr_y, int ox, int oy);
static int do_save(void);
static int do_png_save(FILE * fi, const char * const fname, SDL_Surface * surf);
static void get_new_file_id(void);
static int do_quit(void);
void do_open(void);
static void wait_for_sfx(void);
static void rgbtohsv(Uint8 r8, Uint8 g8, Uint8 b8, float *h, float *s, float *v);
static void hsvtorgb(float h, float s, float v, Uint8 *r8, Uint8 *g8, Uint8 *b8);

static void show_progress_bar(void);
static int progress_bar_disabled;

SDL_Surface * flip_surface(SDL_Surface *s);
SDL_Surface * mirror_surface(SDL_Surface *s);

static void do_print(void);
static void strip_trailing_whitespace(char * buf);
static void do_render_cur_text(int do_blit);
static void loadfonts(const char * const dir);
static char * uppercase(char * str);
static char * textdir(const char * const str);
static SDL_Surface * do_render_button_label(const char * const label);
static void create_button_labels(void);
static int colors_close(Uint32 c1, Uint32 c2);
static void do_flood_fill(int x, int y, Uint32 cur_colr, Uint32 old_colr);
static Uint32 scrolltimer_callback(Uint32 interval, void *param);
static Uint32 drawtext_callback(Uint32 interval, void *param);
static void control_drawtext_timer(Uint32 interval, const char * const text);
static void parse_options(FILE * fi);
static void do_setcursor(SDL_Cursor * c);
static const char * great_str(void);
static void draw_image_title(int t, SDL_Rect dest);
static void handle_keymouse(SDLKey key, Uint8 updown);
static void handle_active(SDL_Event * event);
static char * remove_slash(char * path);
static void anti_carriage_return(int left, int right, int cur_top, int new_top,
		          int cur_bot, int line_width);
static int mySDL_WaitEvent(SDL_Event *event);
static int mySDL_PollEvent(SDL_Event *event);
static void load_starter_id(char * saved_id);
static void load_starter(char * img_id);
static SDL_Surface * duplicate_surface(SDL_Surface * orig);
static void mirror_starter(void);
static void flip_starter(void);
int valid_click(Uint8 button);

#ifdef DEBUG
static char * debug_gettext(const char * str);
static int charsize(char c);
#endif


#define MAX_UTF8_CHAR_LENGTH 6

#define USEREVENT_TEXT_UPDATE 1

static int bypass_splash_wait;

// Wait for a keypress or mouse click
// counter is in 1/10 second units
static void do_wait(int counter)
{
  SDL_Event event;
  int done;

  if(bypass_splash_wait)
    return;

  done = 0;

  do
    {
      while (mySDL_PollEvent(&event))
	{
	  if (event.type == SDL_QUIT)
	    {
	      done = 1;

	      /* FIXME: Handle SDL_Quit better */
	    }
	  else if (event.type == SDL_ACTIVEEVENT)
	    {
	      handle_active(&event);
	    }
	  else if (event.type == SDL_KEYDOWN)
	    {
	      done = 1;
	    }
	  else if (event.type == SDL_MOUSEBUTTONDOWN &&
		   valid_click(event.button.button))
	    {
	      done = 1;
	    }
	}

      counter--;
      SDL_Delay(100);
    }
  while (!done && counter > 0);
}


// This lets us exit quickly; perhaps the system is swapping to death
// or the user started Tux Paint by accident. It also lets the user
// more easily bypass the splash screen wait.
static void eat_sdl_events(void)
{
  SDL_Event event;
  
  while (SDL_PollEvent(&event))
    {
      if (event.type == SDL_QUIT)
      {
	SDL_Quit();
        exit(0);  // can't safely use do_quit during start-up
      }
      else if (event.type == SDL_ACTIVEEVENT)
        handle_active(&event);
      else if (event.type == SDL_KEYDOWN)
        {
          SDLKey key  = event.key.keysym.sym;
          SDLMod ctrl = event.key.keysym.mod & KMOD_CTRL;
          SDLMod alt  = event.key.keysym.mod & KMOD_ALT;
          if ((key==SDLK_c && ctrl) || (key==SDLK_F4 && alt))
	  {
            SDL_Quit();
            exit(0);
	  }
          else if (key==SDLK_ESCAPE && waiting_for_fonts)
	  {
	    /* abort font loading! */

	    printf("Aborting font load!\n");

	    font_thread_aborted = 1;
	    //waiting_for_fonts = 0;
	  }
	  else
            bypass_splash_wait = 1;
        }
      else if (event.type == SDL_MOUSEBUTTONDOWN)
        bypass_splash_wait = 1;
    }
}


static void show_progress_bar(void)
{
  SDL_Rect dest, src;
  int x;
  static Uint32 oldtime;
  Uint32 newtime;

  if(progress_bar_disabled)
    return;

  newtime = SDL_GetTicks();
  if(newtime > oldtime+15)  // trying not to eat some serious CPU time!
    {
      for (x = 0; x < WINDOW_WIDTH; x = x + 65)
        {
          src.x = 65 - (prog_bar_ctr % 65);
          src.y = 0;
          src.w = 65;
          src.h = 24;

          dest.x = x;
          dest.y = WINDOW_HEIGHT - 24;
      
          SDL_BlitSurface(img_progress, &src, screen, &dest);
        }

      prog_bar_ctr++;

      SDL_UpdateRect(screen, 0, WINDOW_HEIGHT - 24, WINDOW_WIDTH, 24);
    }
  oldtime = newtime;

  eat_sdl_events();
}



#ifdef __powerpc__
// Ticks at 1/4  the memory bus clock (24.907667 MHz on Albert's Mac Cube)
// This is good for 80-second diff or 160-second total.
#define CLOCK_ASM(tbl) asm volatile("mftb %0" : "=r" (tbl))
#define CLOCK_TYPE unsigned long
#ifndef CLOCK_SPEED
#warning Benchmark times are based on a 99.63 MHz memory bus.
#define CLOCK_SPEED 24907667.0
#endif
#endif

#ifdef __i386__
#define CLOCK_ASM(tbl) asm volatile("rdtsc" : "=A" (tbl))
#define CLOCK_TYPE unsigned long long
#ifndef CLOCK_SPEED
#warning Benchmark times are based on a 450 MHz CPU.
#define CLOCK_SPEED 450000000.0
#endif
#endif

#ifndef CLOCK_ASM
#warning No idea how to read CPU cycles for you, sorry.
#define CLOCK_ASM(tbl)
#define CLOCK_TYPE unsigned long
#define CLOCK_SPEED 1000000000.0
#endif

#ifdef NO_ASM
#undef CLOCK_ASM
#define CLOCK_ASM(x) x=42
#endif

/* --- MAIN --- */

int main(int argc, char * argv[])
{
  CLOCK_TYPE time1;
  CLOCK_TYPE time2;
  SDL_Rect dest;
  SDL_Rect src;

#ifdef FORKED_FONTS
  run_font_scanner();
#endif

  /* Set up locale support */
  setlocale(LC_ALL, "");

  CLOCK_ASM(time1);

  /* Set up! */
  setup(argc, argv);



#if 0
  while(!font_thread_done)
    {
      // FIXME: should respond to quit events
      // FIXME: should have a read-depends memory barrier around here
      show_progress_bar();
      SDL_Delay(20);
    }
  SDL_WaitThread(font_thread, NULL);
#endif

  CLOCK_ASM(time2);

#ifdef DEBUG
  printf("Start-up time: %.3f\n", (double)(time2-time1)/CLOCK_SPEED);
#endif

  // Let the user know we're (nearly) ready now

  dest.x = 0;
  dest.y = WINDOW_HEIGHT - img_progress->h;
  dest.h = img_progress->h;
  dest.w = WINDOW_WIDTH;
  SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 255, 255, 255));
  src.h = img_progress->h;
  src.w = img_title->w;
  src.x = 0;
  src.y = img_title->h - img_progress->h;
  dest.x = ((WINDOW_WIDTH - img_title->w - (img_title_tuxpaint->w / 2)) / 2) + (img_title_tuxpaint->w / 2) + 20;
  SDL_BlitSurface(img_title, &src, screen, &dest);

  SDL_FreeSurface(img_title);
  SDL_FreeSurface(img_title_credits);
  SDL_FreeSurface(img_title_tuxpaint);

  dest.x = 0;
  dest.w = WINDOW_WIDTH;  // SDL mangles this! So, do repairs.
  update_screen_rect(&dest);

  do_setcursor(cursor_arrow);
  playsound(0, SND_HARP, 1);
  do_wait(50);  // about 5 seconds


  /* Set defaults! */

  cur_undo = 0;
  oldest_undo = 0;
  newest_undo = 0;

  cur_tool = TOOL_BRUSH;
  cur_color = COLOR_BLACK;
  colors_are_selectable = 1;
  cur_brush = 0;
  cur_stamp = 0;
  cur_shape = SHAPE_SQUARE;
  cur_magic = 0;
  cur_font = 0;
  cur_eraser = 0;
  cursor_left = -1;
  cursor_x = -1;
  cursor_y = -1;
  cursor_textwidth = 0;

  mouse_x = WINDOW_WIDTH / 2;
  mouse_y = WINDOW_HEIGHT / 2;
  SDL_WarpMouse(mouse_x, mouse_y);

  mousekey_up = SDL_KEYUP;
  mousekey_down = SDL_KEYUP;
  mousekey_left = SDL_KEYUP;
  mousekey_right = SDL_KEYUP;

  eraser_sound = 0;

  img_cur_brush = NULL;
  render_brush();

  brush_scroll = 0;
  stamp_scroll = 0;
  font_scroll = 0;
  magic_scroll = 0;


  reset_avail_tools();


  /* Load current image (if any): */

  load_current();
  been_saved = 1;
  tool_avail[TOOL_SAVE] = 0;


  /* Draw the screen! */

  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 255, 255, 255));

  draw_toolbar();
  draw_colors(COLORSEL_REFRESH);
  draw_brushes();
  update_canvas(0, 0, WINDOW_WIDTH - 96, (48 * 7) + 40 + HEIGHTOFFSET);

  SDL_Flip(screen);

  draw_tux_text(tool_tux[cur_tool], tool_tips[cur_tool], 1);

  /* Main loop! */
  mainloop();


  /* Close and quit! */

  save_current();

  wait_for_sfx();


  cleanup();

  return 0;
}


/* FIXME: Move elsewhere!!! */

#define PROMPT_QUIT_TXT gettext_noop("Do you really want to quit?")
#define PROMPT_QUIT_YES gettext_noop("Yes")
#define PROMPT_QUIT_NO  gettext_noop("No")

#define PROMPT_QUIT_SAVE_TXT gettext_noop("If you quit, you’ll lose your picture! Save it?")
#define PROMPT_QUIT_SAVE_YES gettext_noop("Yes")
#define PROMPT_QUIT_SAVE_NO gettext_noop("No")

#define PROMPT_OPEN_SAVE_TXT gettext_noop("Save your picture first?")
#define PROMPT_OPEN_SAVE_YES gettext_noop("Yes")
#define PROMPT_OPEN_SAVE_NO gettext_noop("No")

#define PROMPT_OPEN_UNOPENABLE_TXT gettext_noop("Can’t open that picture!")
#define PROMPT_OPEN_UNOPENABLE_YES gettext_noop("OK")

#define PROMPT_NEW_TXT gettext_noop("Starting a new picture will erase the current one!")
#define PROMPT_NEW_YES gettext_noop("That’s OK!")
#define PROMPT_NEW_NO gettext_noop("Never mind!")

#define PROMPT_OPEN_NOFILES_TXT gettext_noop("There are no saved files!")
#define PROMPT_OPEN_NOFILES_YES gettext_noop("OK")

#define PROMPT_PRINT_NOW_TXT gettext_noop("Print your picture now?")
#define PROMPT_PRINT_NOW_YES gettext_noop("Yes")
#define PROMPT_PRINT_NOW_NO gettext_noop("No")

#define PROMPT_PRINT_TXT gettext_noop("Your picture has been printed!")
#define PROMPT_PRINT_YES gettext_noop("OK")

#define PROMPT_PRINT_TOO_SOON_TXT gettext_noop("You can’t print yet!")
#define PROMPT_PRINT_TOO_SOON_YES gettext_noop("OK")

#define PROMPT_ERASE_TXT gettext_noop("Erase this picture?")
#define PROMPT_ERASE_YES gettext_noop("Yes")
#define PROMPT_ERASE_NO gettext_noop("No")

#define PROMPT_TIP_LEFTCLICK_TXT gettext_noop("Remember to use the left mouse button!")
#define PROMPT_TIP_LEFTCLICK_YES gettext_noop("OK")


enum {
  SHAPE_TOOL_MODE_STRETCH,
  SHAPE_TOOL_MODE_ROTATE,
  SHAPE_TOOL_MODE_DONE
};


/* --- MAIN LOOP! --- */

static void mainloop(void)
{
  int done, which, button_down, old_x, old_y, new_x, new_y,
    line_start_x, line_start_y, shape_tool_mode,
    shape_ctr_x, shape_ctr_y, shape_outer_x, shape_outer_y;
  int num_things;
  int *thing_scroll;
  int cur_thing, do_draw, old_tool,
    tmp_int, max;
  int cur_time, last_print_time, scrolling, ignoring_motion;
  SDL_TimerID scrolltimer;
  SDL_Event event;
  SDLKey key, key_down;
  Uint16 key_unicode;
  SDLMod mod;
  Uint32 last_cursor_blink, cur_cursor_blink,
    pre_event_time, current_event_time;


  num_things = num_brushes;
  thing_scroll = &brush_scroll;
  cur_thing = 0;
  do_draw = 0;
  old_x = 0;
  old_y = 0;
  new_x = 0;
  new_y = 0;
  line_start_x = 0;
  line_start_y = 0;
  shape_ctr_x = 0;
  shape_ctr_y = 0;
  shape_outer_x = 0;
  shape_outer_y =0;
  shape_tool_mode = SHAPE_TOOL_MODE_DONE;
  button_down = 0;
  last_print_time = -print_delay;
  last_cursor_blink = 0;
  texttool_len = 0;
  scrolling = 0;
  scrolltimer = 0;
  key_down = SDLK_LAST;
  key_unicode = 0;


  done = 0;

  do
    {
      ignoring_motion = 0;

      pre_event_time = SDL_GetTicks();


      while (mySDL_PollEvent(&event))
	{
	  current_event_time = SDL_GetTicks();

	  if (current_event_time > pre_event_time + 250)
	    ignoring_motion = 1;


	  if (event.type == SDL_QUIT)
	    {
	      done = do_quit();
	    }
	  else if (event.type == SDL_ACTIVEEVENT)
	    {
	      handle_active(&event);
	    }
	  else if (event.type == SDL_KEYUP)
	    {
	      key = event.key.keysym.sym;

	      handle_keymouse(key, SDL_KEYUP);
	    }
	  else if (event.type == SDL_KEYDOWN)
	    {
	      key = event.key.keysym.sym;
	      mod = event.key.keysym.mod;
	      
	      handle_keymouse(key, SDL_KEYDOWN);

	      if (key == SDLK_ESCAPE && !disable_quit)
		{
		  done = do_quit();
		}
	      else if (key == SDLK_s && (mod & KMOD_ALT))
	        {
		  if (use_sound)
		  {
		    mute = !mute;
		    Mix_HaltChannel(-1);
		  }
	        }
	      else if (key == SDLK_ESCAPE &&
		       (mod & KMOD_SHIFT) && (mod & KMOD_CTRL))
		{
		  done = do_quit();
		}
#ifdef WIN32
	      else if (key == SDLK_F4 && (mod & KMOD_ALT))
	        {
		  done = do_quit();
	        }
#endif
	      else if (key==SDLK_z && (mod & KMOD_CTRL) && !noshortcuts)
		{
		  /* Ctrl-Z - Undo */
		  
		  if (tool_avail[TOOL_UNDO])
		    {
		      if (cur_undo == newest_undo)
			{
			  rec_undo_buffer();
			  do_undo();
			}
		      do_undo();
		      update_screen_rect(&r_tools);
		      shape_tool_mode = SHAPE_TOOL_MODE_DONE;
		    }
		}
	      else if (key==SDLK_r && (mod & KMOD_CTRL) && !noshortcuts)
		{
		  /* Ctrl-R - Redo */
		  
		  if (tool_avail[TOOL_REDO])
		    {
		      do_redo();
		      update_screen_rect(&r_tools);
		      shape_tool_mode = SHAPE_TOOL_MODE_DONE;
		    }
		}
	      else if (key==SDLK_o && (mod & KMOD_CTRL) && !noshortcuts)
		{
		  /* Ctrl-O - Open */
		  
		  disable_avail_tools();
		  draw_toolbar();
		  draw_colors(COLORSEL_CLOBBER);
		  draw_none();
		  
		  do_open();
		  
		  enable_avail_tools();
		  
		  draw_toolbar();
		  update_screen_rect(&r_tools);
		  draw_colors(COLORSEL_REFRESH);

		  if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES)
		    draw_brushes();
		  else if (cur_tool == TOOL_MAGIC)
		    draw_magic();
		  else if (cur_tool == TOOL_STAMP)
		    draw_stamps();
		  else if (cur_tool == TOOL_TEXT)
		    draw_fonts();
		  else if (cur_tool == TOOL_SHAPES)
		    draw_shapes();
		  else if (cur_tool == TOOL_ERASER)
		    draw_erasers();
		  
		  draw_tux_text(TUX_GREAT, tool_tips[cur_tool], 1);

		  /* FIXME: Make delay configurable: */
		  control_drawtext_timer(1000, tool_tips[cur_tool]);
		}
	      else if ( (key==SDLK_n && (mod & KMOD_CTRL)) && !noshortcuts)
		{
		  /* Ctrl-N - New */
		  
		  if (do_prompt_snd(PROMPT_NEW_TXT,
				    PROMPT_NEW_YES,
				    PROMPT_NEW_NO,
				    SND_AREYOUSURE))
		    {
		      free_surface(&img_starter);
		      free_surface(&img_starter_bkgd);
		      starter_mirrored = 0;
		      starter_flipped = 0;
		      
		      SDL_FillRect(canvas, NULL,
				   SDL_MapRGB(canvas->format, 255, 255, 255));
		      update_canvas(0, 0,
				    WINDOW_WIDTH - 96,
				    (48 * 7) + 40 + HEIGHTOFFSET);

		      cur_undo = 0;
		      oldest_undo = 0;
		      newest_undo = 0;
		      shape_tool_mode = SHAPE_TOOL_MODE_DONE;
		      
		      been_saved = 1;
		      reset_avail_tools();
		      
		      file_id[0] = '\0';
		      starter_id[0] = '\0';
		      
		      playsound(1, SND_HARP, 1);
		    }
		  else
		    {
		      draw_tux_text(tool_tux[TUX_DEFAULT], TIP_NEW_ABORT,
				    1);
		    }
		  
		  draw_toolbar();
		  update_screen_rect(&r_tools);
		}
	      else if (key==SDLK_s && (mod & KMOD_CTRL) && !noshortcuts)
		{
		  /* Ctrl-S - Save */
		  
		  if (do_save())
		    {
		      /* Only think it's been saved if it HAS been saved :^) */
		      
		      been_saved = 1;
		      tool_avail[TOOL_SAVE] = 0;
		    }

		  /* cur_tool = old_tool; */
		  draw_toolbar();
		  update_screen_rect(&r_tools);
		}
	      else
		{
		  /* Handle key in text tool: */

		  if (cur_tool == TOOL_TEXT &&
		      cursor_x != -1 && cursor_y != -1)
		    {
		      key_down = key;
		      key_unicode = event.key.keysym.unicode;

#ifdef DEBUG
		      printf("charsize(%c) = %d\n", event.key.keysym.unicode,
			     charsize(event.key.keysym.unicode));
#endif
	
		      if (key_down == SDLK_BACKSPACE)
			{

			  if (texttool_len > 0)
			    {
			      texttool_len--;
			      texttool_str[texttool_len] = '\0';
			      playsound(0, SND_KEYCLICK, 0);
	        
			      do_render_cur_text(0);
			    }
			}
		      else if (key_down == SDLK_RETURN)
			{
			  int font_height;
			  if (texttool_len > 0)
			    {
			      rec_undo_buffer();
			      do_render_cur_text(1);
			      texttool_len = 0;
			      cursor_textwidth = 0;
			    }
			  font_height = TTF_FontHeight(getfonthandle(cur_font));

			  cursor_x = cursor_left;
			  cursor_y = min(cursor_y+font_height, canvas->h-font_height);
	    
			  playsound(0, SND_RETURN, 1);
			}
		      else if (key_down == SDLK_TAB)
		        {
			  if (texttool_len > 0)
			    {
			      rec_undo_buffer();
			      do_render_cur_text(1);
			      cursor_x = min(cursor_x + cursor_textwidth,
					     canvas->w);
			      texttool_len = 0;
			      cursor_textwidth = 0;
			    }
		        }
		      else if (isprint(key_unicode))
			{
			  if (texttool_len < sizeof(texttool_str) - MAX_UTF8_CHAR_LENGTH)
			    {
			      int old_cursor_textwidth = cursor_textwidth;
#ifdef DEBUG
			      printf("    key = %c\n"
				     "unicode = %c (%d)\n\n",
				     key_down, key_unicode, key_unicode);
#endif
	  	     
			        texttool_str[texttool_len++] = key_unicode;
		
			      texttool_str[texttool_len] = '\0';
			      do_render_cur_text(0);


			      if (cursor_x + old_cursor_textwidth <= canvas->w - 50 &&
				  cursor_x + cursor_textwidth > canvas->w - 50)
			      {
				playsound(0, SND_KEYCLICKRING, 1);
			      }
			      else
			      {
				playsound(0, SND_KEYCLICK, 0);
			      }
			    }
			}
		    }
		}
	    }
	  else if (event.type == SDL_MOUSEBUTTONDOWN &&
		   valid_click(event.button.button))
	    {
	      if (HIT(r_tools))
		{
		  /* A tool on the left has been pressed! */
		  which = GRIDHIT_GD(r_tools,gd_tools);
		  
		  if (which < NUM_TOOLS && tool_avail[which])
		    {
		      /* Render any current text: */
		      
		      if (cur_tool == TOOL_TEXT && which != TOOL_TEXT &&
			  texttool_len > 0)
			{
			  if (cursor_x != -1 && cursor_y != -1)
			    {
			      if (texttool_len > 0)
				{
				  rec_undo_buffer();
				  do_render_cur_text(1);
				  texttool_len = 0;
				  cursor_textwidth = 0;
				}
			    }
			}
		      
		      
		      old_tool = cur_tool;
		      cur_tool = which;
		      draw_toolbar();
		      update_screen_rect(&r_tools);
		      
		      playsound(1, SND_CLICK, 0);

                      // FIXME: this "if" is just plain gross
		      if(cur_tool != TOOL_TEXT)
		        draw_tux_text(tool_tux[cur_tool], tool_tips[cur_tool], 1);
		      
		      /* Draw items for this tool: */
		      
		      if (cur_tool == TOOL_BRUSH)
			{
			  cur_thing = cur_brush;
			  num_things = num_brushes;
			  thing_scroll = &brush_scroll;
			  draw_brushes();
			  draw_colors(COLORSEL_ENABLE);
			}
		      else if (cur_tool == TOOL_STAMP)
			{
			  cur_thing = cur_stamp;
			  num_things = num_stamps;
			  thing_scroll = &stamp_scroll;
			  draw_stamps();
			  draw_colors(stamp_colorable(cur_stamp) ||
				      stamp_tintable(cur_stamp));
		          set_active_stamp();
			  update_stamp_xor();
			}
		      else if (cur_tool == TOOL_LINES)
			{
			  cur_thing = cur_brush;
			  num_things = num_brushes;
			  thing_scroll = &brush_scroll;
			  draw_brushes();
			  draw_colors(COLORSEL_ENABLE);
			}
		      else if (cur_tool == TOOL_SHAPES)
			{
			  cur_thing = cur_shape;
			  num_things = NUM_SHAPES;
			  thing_scroll = &shape_scroll;
			  draw_shapes();
			  draw_colors(COLORSEL_ENABLE);
			  shape_tool_mode = SHAPE_TOOL_MODE_DONE;
			}
		      else if (cur_tool == TOOL_TEXT)
			{
			  if(!font_thread_done)
			    {
                              draw_colors(COLORSEL_DISABLE);
                              draw_none();
                              update_screen_rect(&r_toolopt);
                              update_screen_rect(&r_ttoolopt);
  			      do_setcursor(cursor_watch);
                              draw_tux_text(TUX_WAIT, gettext("Please wait..."), 1);
			      waiting_for_fonts = 1;
#ifdef FORKED_FONTS
                              receive_some_font_info();
#else
                              while(!font_thread_done && !font_thread_aborted)
                                {
                                  // FIXME: should have a read-depends memory barrier around here
                                  show_progress_bar();
                                  SDL_Delay(20);
                                }
                              // FIXME: should kill this in any case
                              SDL_WaitThread(font_thread, NULL);
#endif
  			      do_setcursor(cursor_arrow);
                            }
		          draw_tux_text(tool_tux[cur_tool], tool_tips[cur_tool], 1);

			  if (num_font_families > 0)
			  {
			    cur_thing = cur_font;
			    num_things = num_font_families;
			    thing_scroll = &font_scroll;
			    draw_fonts();
			    draw_colors(COLORSEL_ENABLE);
			  }
			  else
			  {
			    /* Problem using fonts! */

			    cur_tool = old_tool;
			    draw_toolbar();
			    update_screen_rect(&r_tools);
			  }
			}
		      else if (cur_tool == TOOL_MAGIC)
			{
			  cur_thing = cur_magic;
			  num_things = NUM_MAGICS;
			  thing_scroll = &magic_scroll;
			  rainbow_color = 0;
			  draw_magic();
			  draw_colors(magic_colors[cur_magic]);
			}
		      else if (cur_tool == TOOL_ERASER)
			{
			  cur_thing = cur_eraser;
			  num_things = NUM_ERASERS;
			  thing_scroll = &eraser_scroll;
			  draw_erasers();
			  draw_colors(COLORSEL_DISABLE);
			}
		      else if (cur_tool == TOOL_UNDO)
			{
			  if (cur_undo == newest_undo)
			    {
			      rec_undo_buffer();
			      do_undo();
			    }
			  do_undo();
			  
			  been_saved = 0;

			  if (!disable_save)
			    tool_avail[TOOL_SAVE] = 1;
			  
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			  shape_tool_mode = SHAPE_TOOL_MODE_DONE;
			}
		      else if (cur_tool == TOOL_REDO)
			{
			  do_redo();
			  
			  been_saved = 0;

			  if (!disable_save)
			    tool_avail[TOOL_SAVE] = 1;
			  
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			  shape_tool_mode = SHAPE_TOOL_MODE_DONE;
			}
		      else if (cur_tool == TOOL_OPEN)
			{
			  disable_avail_tools();
			  draw_toolbar();
		          draw_colors(COLORSEL_CLOBBER);
		          draw_none();
			  
			  do_open();
			  
			  enable_avail_tools();
			  
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			  
			  draw_tux_text(TUX_GREAT, tool_tips[cur_tool], 1);

			  draw_colors(COLORSEL_REFRESH);

			  if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES)
			    draw_brushes();
			  else if (cur_tool == TOOL_MAGIC)
			    draw_magic();
			  else if (cur_tool == TOOL_STAMP)
			    draw_stamps();
			  else if (cur_tool == TOOL_TEXT)
			    draw_fonts();
			  else if (cur_tool == TOOL_SHAPES)
			    draw_shapes();
			  else if (cur_tool == TOOL_ERASER)
			    draw_erasers();
			}
		      else if (cur_tool == TOOL_SAVE)
			{
			  if (do_save())
			    {
			      been_saved = 1;
			      tool_avail[TOOL_SAVE] = 0;
			    }
			  
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			}
		      else if (cur_tool == TOOL_NEW)
			{
			  if (do_prompt_snd(PROMPT_NEW_TXT,
					    PROMPT_NEW_YES,
					    PROMPT_NEW_NO,
					    SND_AREYOUSURE))
			    {
		      	      free_surface(&img_starter);
			      free_surface(&img_starter_bkgd);
			      starter_mirrored = 0;
			      starter_flipped = 0;
		      
			      SDL_FillRect(canvas, NULL,
					   SDL_MapRGB(canvas->format,
						      255, 255, 255));
			      update_canvas(0, 0,
					    WINDOW_WIDTH - 96,
					    (48 * 7) + 40 + HEIGHTOFFSET);
			      
			      cur_undo = 0;
			      oldest_undo = 0;
			      newest_undo = 0;
			      shape_tool_mode = SHAPE_TOOL_MODE_DONE;
			      
			      been_saved = 1;
			      reset_avail_tools();
			      
			      file_id[0] = '\0';
			      starter_id[0] = '\0';
			      
			      playsound(1, SND_HARP, 1);
			    }
			  else
			    {
			      draw_tux_text(tool_tux[TUX_DEFAULT],
					    TIP_NEW_ABORT, 1);
			    }
			  
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			}
		      else if (cur_tool == TOOL_PRINT)
			{
			  cur_time = SDL_GetTicks() / 1000;
			  
#ifdef DEBUG
			  printf("Current time = %d\n", cur_time);
#endif
			  
			  if (cur_time >= last_print_time + print_delay)
			    {
			      if (alt_print_command_default == ALTPRINT_ALWAYS)
				want_alt_printcommand = 1;
			      else if (alt_print_command_default == ALTPRINT_NEVER)
				want_alt_printcommand = 0;
			      else /* ALTPRINT_MOD */
			        want_alt_printcommand = (SDL_GetModState() & KMOD_ALT);
			      
		              if (do_prompt_image_snd(PROMPT_PRINT_NOW_TXT,
						      PROMPT_PRINT_NOW_YES,
						      PROMPT_PRINT_NOW_NO,
						      img_printer, NULL, NULL,
						      SND_AREYOUSURE))
			      {
			        do_print();
				
			        last_print_time = cur_time;
			      }
			    }
			  else
			    {
			      do_prompt_image_snd(PROMPT_PRINT_TOO_SOON_TXT,
						  PROMPT_PRINT_TOO_SOON_YES,
						  "",
						  img_printer_wait, NULL, NULL,
						  SND_YOUCANNOT);
			    }
			  
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			}
		      else if (cur_tool == TOOL_QUIT)
			{
			  done = do_quit();
			  cur_tool = old_tool;
			  draw_toolbar();
			  update_screen_rect(&r_tools);
			}
		      update_screen_rect(&r_toolopt);
		      update_screen_rect(&r_ttoolopt);
		    }
		}
	      else if (HIT(r_toolopt))
		{
		  // Options on the right
                  // WARNING: this must be kept in sync with the mouse-move
                  // code (for cursor changes) and mouse-scroll code.
		  
		  if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_STAMP ||
		      cur_tool == TOOL_SHAPES || cur_tool == TOOL_LINES ||
		      cur_tool == TOOL_MAGIC || cur_tool == TOOL_TEXT ||
		      cur_tool == TOOL_ERASER)
		    {
		      int old_thing;
	              int num_rows_needed;
		      SDL_Rect r_controls;
		      SDL_Rect r_notcontrols;
		      SDL_Rect r_items;// = r_notcontrols;
	              int toolopt_changed;

		      grid_dims gd_controls = {0,0}; // might become 2-by-2
		      grid_dims gd_items = {2,2}; // generally becoming 2-by-whatever

		      /* Note set of things we're dealing with */
		      /* (stamps, brushes, etc.) */
		      
		      if (cur_tool == TOOL_STAMP)
			{
			  if(!disable_stamp_controls)
			    gd_controls = (grid_dims){2,2};
			}
		      else if (cur_tool == TOOL_TEXT)
			{
			  if(!disable_stamp_controls)
			    gd_controls = (grid_dims){2,2};
			}

		      // number of whole or partial rows that will be needed
		      // (can make this per-tool if variable columns needed)
	              num_rows_needed = (num_things+gd_items.cols-1)/gd_items.cols;

		      do_draw = 0;

		      r_controls.w = r_toolopt.w;
		      r_controls.h = gd_controls.rows * button_h;
		      r_controls.x = r_toolopt.x;
		      r_controls.y = r_toolopt.y + r_toolopt.h - r_controls.h;
		      
		      r_notcontrols.w = r_toolopt.w;
		      r_notcontrols.h = r_toolopt.h - r_controls.h;
		      r_notcontrols.x = r_toolopt.x;
		      r_notcontrols.y = r_toolopt.y;
		      
		      r_items.x = r_notcontrols.x;
		      r_items.y = r_notcontrols.y;
		      r_items.w = r_notcontrols.w;
		      r_items.h = r_notcontrols.h;

		      if(num_rows_needed * button_h > r_items.h)
		        {
		          // too many; we'll need scroll buttons
		          r_items.h -= button_h;
		          r_items.y += button_h/2;
		        }
	              gd_items.rows = r_items.h / button_h;

	              toolopt_changed = 0;
		      
		      if(HIT(r_items))
		        {
		          which = GRIDHIT_GD(r_items,gd_items) + *thing_scroll;
                          
                          if (which < num_things)
                            {
                              toolopt_changed = 1;
#ifndef NOSOUND
                              if (cur_tool != TOOL_STAMP || stamp_data[which]->ssnd == NULL)
                                { 
                                  playsound(1, SND_BLEEP, 0);
                                }
#endif
                              old_thing = cur_thing;
                              cur_thing = which;
                              do_draw = 1;
                            }
		        }
		      else if(HIT(r_controls))
		        {
		          which = GRIDHIT_GD(r_controls,gd_controls);
		          if (cur_tool == TOOL_STAMP)
		            {
                              /* Stamp controls! */
                              int control_sound = -1;

                              if (which&2)
                                {
#ifdef OLD_STAMP_GROW_SHRINK
                                  /* One of the bottom buttons: */
                                  if (which&1)
                                    {
                                      /* Bottom right button: Grow: */
                                      if (stamp_data[cur_stamp]->size < MAX_STAMP_SIZE)
                                        {
                                          stamp_data[cur_stamp]->size++;
                                          control_sound = SND_GROW;
                                        }
                                    }
                                  else
                                    {
                                      /* Bottom left button: Shrink: */
                                      if (stamp_data[cur_stamp]->size > MIN_STAMP_SIZE)
                                        {
                                          stamp_data[cur_stamp]->size--;
                                          control_sound = SND_SHRINK;
                                        }
                                    }
#else
				  int old_size;

				  old_size = stamp_data[cur_stamp]->size;

				  stamp_data[cur_stamp]->size = (((MAX_STAMP_SIZE - MIN_STAMP_SIZE) * (event.button.x - (WINDOW_WIDTH - 96))) / 96) + MIN_STAMP_SIZE;

				  if (stamp_data[cur_stamp]->size < old_size)
				    control_sound = SND_SHRINK;
				  else if (stamp_data[cur_stamp]->size > old_size)
				    control_sound = SND_GROW;
#endif
                                }
                              else
                                {
                                  /* One of the top buttons: */
                                  if (which&1)
                                    {
                                      /* Top right button: Flip: */
                                      if (stamp_data[cur_stamp]->flipable)
                                        {
                                          stamp_data[cur_stamp]->flipped =
                                            !stamp_data[cur_stamp]->flipped;
                                          control_sound = SND_FLIP;
                                        }
                                    }
                                  else
                                    {
                                      /* Top left button: Mirror: */
                                      if (stamp_data[cur_stamp]->mirrorable)
                                        {
                                          stamp_data[cur_stamp]->mirrored =
                                            !stamp_data[cur_stamp]->mirrored;
                                          control_sound = SND_MIRROR;
                                        }
                                    }
                                }
                              if (control_sound != -1)
                                {
                                  playsound(0, control_sound, 0);
                                  draw_stamps();
                                  update_screen_rect(&r_toolopt);
                                  set_active_stamp();
                                  update_stamp_xor();
                                }
		            }
		          else  // not TOOL_STAMP, so must be TOOL_TEXT
		            {
                              /* Text controls! */
                              int control_sound = -1;
                              if (which&2)
                                {
                                  /* One of the bottom buttons: */
                                  if (which&1)
                                    {
                                      /* Bottom right button: Grow: */
                                      if (text_size < MAX_TEXT_SIZE)
                                        {
                                          text_size++;
                                          control_sound = SND_GROW;
				          toolopt_changed = 1;
                                        }
                                    }
                                  else
                                    {
                                      /* Bottom left button: Shrink: */
                                      if (text_size > MIN_TEXT_SIZE)
                                        {
                                          text_size--;
                                          control_sound = SND_SHRINK;
				          toolopt_changed = 1;
                                        }
                                    }
                                }
                              else
                                {
                                  /* One of the top buttons: */
                                  if (which&1)
                                    {
                                      /* Top right button: Italic: */
                                      if (text_state & TTF_STYLE_ITALIC)
                                        {
                                          text_state &= ~TTF_STYLE_ITALIC;
                                          control_sound = SND_ITALIC_ON;
                                        }
                                      else
                                        {
                                          text_state |= TTF_STYLE_ITALIC;
                                          control_sound = SND_ITALIC_OFF;
                                        }
                                    }
                                  else
                                    {
                                      /* Top left button: Bold: */
                                      if (text_state & TTF_STYLE_BOLD)
                                        {
                                          text_state &= ~TTF_STYLE_BOLD;
                                          control_sound = SND_THIN;
                                        }
                                      else
                                        {
                                          text_state |= TTF_STYLE_BOLD;
                                          control_sound = SND_THICK;
                                        }
                                    }
				  toolopt_changed = 1;
                                }


                              if (control_sound != -1)
                                {
                                  playsound(0, control_sound, 0);


				  if (cur_tool == TOOL_TEXT) // Huh? It had better be!
				  {
                                    // need to invalidate all the cached user fonts, causing reload on demand
                                    int i;
                                    for (i = 0; i < num_font_families; i++)
                                      {
                                        if (user_font_families[i] && user_font_families[i]->handle)
                                          {
                                            TTF_CloseFont(user_font_families[i]->handle);
                                            user_font_families[i]->handle = NULL;
                                          }
                                      }
                                    // FIXME: is setting do_draw enough?
                                    draw_fonts();
                                    update_screen_rect(&r_toolopt);
				  }
                                }
                            }
		        }
		      else
		        {
		          // scroll button
		          int is_upper = event.button.y < r_toolopt.y + button_h/2;
                          if (
                               (is_upper && *thing_scroll > 0)  // upper arrow
                               ||
                               (!is_upper && *thing_scroll/gd_items.cols < num_rows_needed-gd_items.rows) // lower arrow
                             )
                            {
                              *thing_scroll += is_upper ? -gd_items.cols : gd_items.cols;
                              do_draw = 1;
                              playsound(1, SND_SCROLL, 1);
                              if (!scrolling)
                                {
                                  memcpy(&scrolltimer_event, &event, sizeof(SDL_Event));
                                  /* FIXME: Make delay value changable: */
                                  scrolltimer = SDL_AddTimer(REPEAT_SPEED, scrolltimer_callback, (void*) &scrolltimer_event);
                                  scrolling = 1;
                                }
                              else
                                {
                                  SDL_RemoveTimer(scrolltimer);
                                  scrolltimer = SDL_AddTimer(REPEAT_SPEED / 3, scrolltimer_callback, (void*) &scrolltimer_event);
                                }
                              if (*thing_scroll == 0)
                                {
                                  do_setcursor(cursor_arrow);
                                  if (scrolling)
                                    {
                                      SDL_RemoveTimer(scrolltimer);
                                      scrolling = 0;
                                    }
                                }
                            }
		        }
                      
                      
                      /* Assign the change(s), if any / redraw, if needed: */
                      
                      if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES)
                        {
                          cur_brush = cur_thing;
                          render_brush();
                          
                          if (do_draw)
                            draw_brushes();
                        }
                      else if (cur_tool == TOOL_ERASER)
                        {
                          cur_eraser = cur_thing;
                          
                          if (do_draw)
                            draw_erasers();
                        }
                      else if (cur_tool == TOOL_TEXT)
                        {
                          char font_tux_text[512];

                          cur_font = cur_thing;
                          
                          snprintf(font_tux_text, sizeof font_tux_text, "%s (%s).", TTF_FontFaceFamilyName(getfonthandle(cur_font)), TTF_FontFaceStyleName(getfonthandle(cur_font)));
//                          printf("font change:%s\n", font_tux_text);
                          draw_tux_text(TUX_GREAT, font_tux_text, 1);
                          
                          if (do_draw)
                            draw_fonts();
                          
			  
			  // Only rerender when picking a different font
			  if (toolopt_changed)
                            do_render_cur_text(0);
                        }
                      else if (cur_tool == TOOL_STAMP)
                        {
#ifndef NOSOUND
                          if (toolopt_changed)
                            {
                              // Only play when picking a different stamp
                              if (stamp_data[cur_thing]->ssnd != NULL && !mute)
                                Mix_PlayChannel(2, stamp_data[cur_thing]->ssnd, 0);
                            }
#endif

                          if (cur_thing != cur_stamp)
                          {
                            cur_stamp = cur_thing;
                            set_active_stamp();
                            update_stamp_xor();
                          }

                          if (do_draw)
                            draw_stamps();
                          
                          if (stamp_data[cur_stamp]->stxt != NULL)
                            {
#ifdef DEBUG
                              printf("stamp_data[cur_stamp]->stxt = %s\n",
                                     stamp_data[cur_stamp]->stxt);
#endif

                              draw_tux_text(TUX_GREAT,
                                                stamp_data[cur_stamp]->stxt, 1);
                            }
                          else
                            draw_tux_text(TUX_GREAT, "", 0);
                          
                          /* Enable or disable color selector: */
                          draw_colors(stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp));
                        }
                      else if (cur_tool == TOOL_SHAPES)
                        {
                          cur_shape = cur_thing;
                          
                          draw_tux_text(TUX_GREAT, shape_tips[cur_shape], 1);
                          
                          if (do_draw)
                            draw_shapes();
                        }
                      else if (cur_tool == TOOL_MAGIC)
                        {
                          if (cur_thing != cur_magic)
                            {
                              cur_magic = cur_thing;
                              draw_colors(magic_colors[cur_magic]);
                            }
                          
                          draw_tux_text(TUX_GREAT, magic_tips[cur_magic], 1);
                          
                          if (do_draw)
                            draw_magic();
                        }
                      
                      /* Update the screen: */
                      if (do_draw)
                        update_screen_rect(&r_toolopt);
                    }
                }
	      else if (HIT(r_colors) && colors_are_selectable)
		{
		  /* Color! */
		  which = GRIDHIT_GD(r_colors,gd_colors);
		  
		  if (which < NUM_COLORS)
		    {
		      cur_color = which;
		      playsound(1, SND_BUBBLE, 1);
		      draw_colors(COLORSEL_REFRESH);
		      render_brush();
		      draw_tux_text(TUX_KISS, color_names[cur_color], 1);
		
		      if (cur_tool == TOOL_TEXT)
		        do_render_cur_text(0);
		    }
		}
	      else if (HIT(r_canvas))
		{
		  /* Draw something! */
		  
		  old_x = event.button.x - r_canvas.x;
		  old_y = event.button.y - r_canvas.y;
		  
		  if (been_saved)
		    {
		      been_saved = 0;

		      if (!disable_save)
		        tool_avail[TOOL_SAVE] = 1;

		      draw_toolbar();
		      update_screen_rect(&r_tools);
		    }
		  
		  if (cur_tool == TOOL_BRUSH)
		    {
		      /* Start painting! */
		      
		      rec_undo_buffer();
		      
		      /* (Arbitrarily large, so we draw once now) */
		      brush_counter = 999;
  	
		      brush_draw(old_x, old_y, old_x, old_y, 1);
		      playsound(0, SND_PAINT1 + (img_cur_brush->w) / 12, 1);
		    }
		  else if (cur_tool == TOOL_STAMP)
		    {
		      /* Draw a stamp! */
		      
		      rec_undo_buffer();
		      
		      stamp_draw(old_x, old_y);
		      stamp_xor(old_x, old_y);
		      playsound(1, SND_STAMP, 1);

		      draw_tux_text(TUX_GREAT, great_str(), 1);

		      /* FIXME: Make delay configurable: */

		      control_drawtext_timer(1000, stamp_data[cur_stamp]->stxt);
		    }
		  else if (cur_tool == TOOL_LINES)
		    {
		      /* Start a line! */
		      
		      rec_undo_buffer();
		      
		      line_start_x = old_x;
		      line_start_y = old_y;
		      
		      /* (Arbitrarily large, so we draw once now) */
		      brush_counter = 999;
		      
		      brush_draw(old_x, old_y, old_x, old_y, 1);
		      
		      playsound(1, SND_LINE_START, 1);
		      draw_tux_text(TUX_BORED, TIP_LINE_START, 1);
		    }
		  else if (cur_tool == TOOL_SHAPES)
		    {
		      if (shape_tool_mode == SHAPE_TOOL_MODE_DONE)
			{
			  /* Start drawing a shape! */
			  
			  rec_undo_buffer();
			  
			  shape_ctr_x = old_x;
			  shape_ctr_y = old_y;
			  
			  shape_tool_mode = SHAPE_TOOL_MODE_STRETCH;
			  
			  playsound(1, SND_LINE_START, 1);
			  draw_tux_text(TUX_BORED, TIP_SHAPE_START, 1);
			}
		      else if (shape_tool_mode == SHAPE_TOOL_MODE_ROTATE)
			{
			  /* Draw the shape with the brush! */
			  
			  /* (Arbitrarily large...) */
			  brush_counter = 999;
			  
			  playsound(1, SND_LINE_END, 1);
			  do_shape(shape_ctr_x, shape_ctr_y,
				   shape_outer_x, shape_outer_y,
				   rotation(shape_ctr_x, shape_ctr_y,
					    event.button.x - r_canvas.x,
					    event.button.y - r_canvas.y),
				   1);
			  
			  shape_tool_mode = SHAPE_TOOL_MODE_DONE;
			  draw_tux_text(TUX_GREAT, tool_tips[TOOL_SHAPES], 1);
			}
		    }
		  else if (cur_tool == TOOL_MAGIC)
		    {
		      /* Start doing magic! */
		     
		      tmp_int = cur_undo;
		      rec_undo_buffer();


		      /* Mirror or flip, make a note so we record it for
		         the starters, too! */

		      if (cur_magic == MAGIC_MIRROR)
			undo_starters[tmp_int] = UNDO_STARTER_MIRRORED;
		      else if (cur_magic == MAGIC_FLIP)
			undo_starters[tmp_int] = UNDO_STARTER_FLIPPED;

		      
		      /* (Arbitrarily large, so we draw once now) */
		      brush_counter = 999;
		     
		      if (cur_magic != MAGIC_FILL)
			{
			  magic_draw(old_x, old_y, old_x, old_y, button_down);
			}
		      else
			{
			  do_flood_fill(old_x, old_y,
					SDL_MapRGB(canvas->format,
						   color_hexes[cur_color][0],
						   color_hexes[cur_color][1],
						   color_hexes[cur_color][2]),
					getpixels[canvas->format->BytesPerPixel](canvas, old_x, old_y));

			  draw_tux_text(TUX_GREAT, magic_tips[MAGIC_FILL], 1);
			}
		      
		      if (cur_magic == MAGIC_FLIP ||
			  cur_magic == MAGIC_MIRROR ||
			  cur_magic == MAGIC_FILL)
			{
			  update_canvas(0, 0, canvas->w, canvas->h);
			}
		    }
		  else if (cur_tool == TOOL_ERASER)
		    {
		      /* Erase! */
		      
		      rec_undo_buffer();
		      
		      do_eraser(old_x, old_y);
		    }
		  else if (cur_tool == TOOL_TEXT)
		    {
		      /* Text Tool! */
		      
		      if (cursor_x != -1 && cursor_y != -1)
			{
		 	  /*
			    if (texttool_len > 0)
			    {
			    rec_undo_buffer();
			    do_render_cur_text(1);
			    texttool_len = 0;
			    }
			  */
			}
		      
		      cursor_x = old_x;
		      cursor_y = old_y;
		      cursor_left = old_x;

		      do_render_cur_text(0);
		    }
		  
		  button_down = 1;
		}
	    }
	  else if (event.type == SDL_MOUSEBUTTONDOWN &&
		   wheely &&
		   event.button.button >= 4 &&
		   event.button.button <= 5)
            {
	      int num_rows_needed;
	      SDL_Rect r_controls;
	      SDL_Rect r_notcontrols;
	      SDL_Rect r_items;// = r_notcontrols;
	      
              // Scroll wheel code.
              // WARNING: this must be kept in sync with the mouse-move
              // code (for cursor changes) and mouse-click code.

              if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_STAMP ||
                  cur_tool == TOOL_SHAPES || cur_tool == TOOL_LINES ||
                  cur_tool == TOOL_MAGIC || cur_tool == TOOL_TEXT ||
                  cur_tool == TOOL_ERASER)
                {
                  grid_dims gd_controls = {0,0}; // might become 2-by-2
                  grid_dims gd_items = {2,2}; // generally becoming 2-by-whatever
		  
                  /* Note set of things we're dealing with */
                  /* (stamps, brushes, etc.) */
                  
                  if (cur_tool == TOOL_STAMP)
                    {
                      if(!disable_stamp_controls)
                        gd_controls = (grid_dims){2,2};
                    }
                  else if (cur_tool == TOOL_TEXT)
                    {
                      if(!disable_stamp_controls)
                        gd_controls = (grid_dims){2,2};
                    }
		  
                  // number of whole or partial rows that will be needed
                  // (can make this per-tool if variable columns needed)
                  num_rows_needed = (num_things+gd_items.cols-1)/gd_items.cols;

                  do_draw = 0;

                  r_controls.w = r_toolopt.w;
                  r_controls.h = gd_controls.rows * button_h;
                  r_controls.x = r_toolopt.x;
                  r_controls.y = r_toolopt.y + r_toolopt.h - r_controls.h;
                  
                  r_notcontrols.w = r_toolopt.w;
                  r_notcontrols.h = r_toolopt.h - r_controls.h;
                  r_notcontrols.x = r_toolopt.x;
                  r_notcontrols.y = r_toolopt.y;
                  
		  r_items.x = r_notcontrols.x;
		  r_items.y = r_notcontrols.y;
		  r_items.w = r_notcontrols.w;
		  r_items.h = r_notcontrols.h;
		  
                  if(num_rows_needed * button_h > r_items.h)
                    {
                      // too many; we'll need scroll buttons
                      r_items.h -= button_h;
                      r_items.y += button_h/2;
                    }
                  gd_items.rows = r_items.h / button_h;

                  if(0)
                    {
                    }
                  else
                    {
                      // scroll button
                      int is_upper = (event.button.button == 4);
                      if (
                           (is_upper && *thing_scroll > 0)  // upper arrow
                           ||
                           (!is_upper && *thing_scroll/gd_items.cols < num_rows_needed-gd_items.rows) // lower arrow
                         )
                        {
                          *thing_scroll += is_upper ? -gd_items.cols : gd_items.cols;
                          do_draw = 1;
                          playsound(1, SND_SCROLL, 1);
#if 0
                          if (!scrolling)
                            {
                              memcpy(&scrolltimer_event, &event, sizeof(SDL_Event));
                              /* FIXME: Make delay value changable: */
                              scrolltimer = SDL_AddTimer(REPEAT_SPEED, scrolltimer_callback, (void*) &scrolltimer_event);
                              scrolling = 1;
                            }
                          else
                            {
                              SDL_RemoveTimer(scrolltimer);
                              scrolltimer = SDL_AddTimer(REPEAT_SPEED / 3, scrolltimer_callback, (void*) &scrolltimer_event);
                            }
#endif
                          if (*thing_scroll == 0)
                            {
                              do_setcursor(cursor_arrow);
#if 0
                              if (scrolling)
                                {
                                  SDL_RemoveTimer(scrolltimer);
                                  scrolling = 0;
                                }
#endif
                            }
                        }
                    }
                  
                  
                  /* Assign the change(s), if any / redraw, if needed: */
                  
                  if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES)
                    {
                      if (do_draw)
                        draw_brushes();
                    }
                  else if (cur_tool == TOOL_ERASER)
                    {
                      if (do_draw)
                        draw_erasers();
                    }
                  else if (cur_tool == TOOL_TEXT)
                    {
                      if (do_draw)
                        draw_fonts();
                    }
                  else if (cur_tool == TOOL_STAMP)
                    {
                      if (do_draw)
                        draw_stamps();
                    }
                  else if (cur_tool == TOOL_SHAPES)
                    {
                      if (do_draw)
                        draw_shapes();
                    }
                  else if (cur_tool == TOOL_MAGIC)
                    {
                      if (do_draw)
                        draw_magic();
                    }
                  
                  /* Update the screen: */
                  if (do_draw)
                    update_screen_rect(&r_toolopt);
                }
            }
	  else if (event.type == SDL_MOUSEBUTTONDOWN &&
		   event.button.button >= 2 &&
		   event.button.button <= 3)
	    {
	      /* They're using the middle or right mouse buttons! */

	      non_left_click_count++;


	      if (non_left_click_count == 10 ||
		  non_left_click_count == 20 ||
		  (non_left_click_count % 50) == 0)
	      {
		/* Pop up an informative animation: */

		do_prompt_image_flash(PROMPT_TIP_LEFTCLICK_TXT,
				      PROMPT_TIP_LEFTCLICK_YES,
				      "",
			              img_mouse, img_mouse_click, NULL, 1);
	      }
	    }
	  else if (event.type == SDL_USEREVENT)
	    {
	      if (event.user.code == USEREVENT_TEXT_UPDATE)
		{
		  /* Time to replace "Great!" with old tip text: */
		    
		  if (event.user.data1 != NULL)
		    {
		      if (((unsigned char *) event.user.data1)[0] == '=')
			{
			  draw_tux_text(TUX_GREAT,
					(char *) event.user.data1 + 1, 1);
			}
		      else
			{
			  draw_tux_text(TUX_GREAT,
					(char *) event.user.data1, 0);
			}
		    }
		  else
		    draw_tux_text(TUX_GREAT, "", 1);
		}
	    }
	  else if (event.type == SDL_MOUSEBUTTONUP)
	    {
	      if (scrolling)
		{
		  SDL_RemoveTimer(scrolltimer);
		  scrolling = 0;
		}
	      
	      if (button_down)
		{
		  if (cur_tool == TOOL_LINES)
		    {
		      /* (Arbitrarily large, so we draw once now) */
		      brush_counter = 999;
		      
		      brush_draw(line_start_x, line_start_y,
				 event.button.x-r_canvas.x, event.button.y-r_canvas.y, 1);
		      brush_draw(event.button.x-r_canvas.x, event.button.y-r_canvas.y,
				 event.button.x-r_canvas.x, event.button.y-r_canvas.y, 1);
		      
		      playsound(1, SND_LINE_END, 1);
		      draw_tux_text(TUX_GREAT, tool_tips[TOOL_LINES], 1);
		    }
		  else if (cur_tool == TOOL_SHAPES)
		    {
		      if (shape_tool_mode == SHAPE_TOOL_MODE_STRETCH)
			{
			  /* Now we can rotate the shape... */
			  
			  shape_outer_x = event.button.x-r_canvas.x;
			  shape_outer_y = event.button.y-r_canvas.y;
			  
			  if (!simple_shapes && !shape_no_rotate[cur_shape])
			    {
			      shape_tool_mode = SHAPE_TOOL_MODE_ROTATE;
		      
			      SDL_WarpMouse(shape_outer_x + 96, shape_ctr_y);
			      do_setcursor(cursor_rotate);
			      
			      
			      /* Erase stretchy XOR: */
			      
			      do_shape(shape_ctr_x, shape_ctr_y, old_x, old_y,
				       0, 0);
			      
			      /* Make an initial rotation XOR to be erased: */
			      
			      do_shape(shape_ctr_x, shape_ctr_y,
				       shape_outer_x, shape_outer_y,
				       rotation(shape_ctr_x, shape_ctr_y,
						shape_outer_x, shape_outer_y),
				       0);
			      
			      playsound(1, SND_LINE_START, 1);
			      draw_tux_text(TUX_BORED, TIP_SHAPE_NEXT, 1);
			      
			      
			      /* FIXME: Do something less intensive! */
			      
			      SDL_Flip(screen);
			    }
			  else
			    {
			      brush_counter = 999; /* arbitrarily large... */
			      
		  
			      playsound(1, SND_LINE_END, 1);
			      do_shape(shape_ctr_x, shape_ctr_y,
				       shape_outer_x, shape_outer_y,
				       0, 1);

			      SDL_Flip(screen);
			      
			      shape_tool_mode = SHAPE_TOOL_MODE_DONE;
			      draw_tux_text(TUX_GREAT,
					    tool_tips[TOOL_SHAPES], 1);
			    }
			}
		    }
		}
	      
	      button_down = 0;
	    }
	  else if (event.type == SDL_MOUSEMOTION && !ignoring_motion)
	    {
	      new_x = event.button.x-r_canvas.x;
	      new_y = event.button.y-r_canvas.y;

	      /* FIXME: Is doing this every event too intensive? */
	      /* Should I check current cursor first? */
	      
	      if (HIT(r_tools))
		{
		  /* Tools: */
		  
		  if (tool_avail[((event.button.x - r_tools.x) / button_w) +
				 ((event.button.y - r_tools.y) / button_h) * gd_tools.cols])
		    {
		      do_setcursor(cursor_hand);
		    }
		  else
		    {
		      do_setcursor(cursor_arrow);
		    }
		}
	      else if (HIT(r_colors))
		{
		  /* Color picker: */
		 
	  	  if (colors_are_selectable)
		    do_setcursor(cursor_hand);
		  else
		    do_setcursor(cursor_arrow);
		}
	      else if (HIT(r_toolopt))
		{
		  // mouse cursor code
                  // WARNING: this must be kept in sync with the mouse-click
                  // and mouse-click code. (it isn't, currently!)
		  
		  /* Note set of things we're dealing with */
		  /* (stamps, brushes, etc.) */
		  
		  if (cur_tool == TOOL_STAMP)
		    {
		    }
		  else if (cur_tool == TOOL_TEXT)
		    {
		    }

		  max = 14;
		  if (cur_tool == TOOL_STAMP && !disable_stamp_controls)
		    max = 10;
		  if (cur_tool == TOOL_TEXT && !disable_stamp_controls)
		    max = 10;
		  
		  
		  if (num_things > max + TOOLOFFSET)
		    {
		      /* Are there scroll buttons? */
		      
		      if (event.button.y < 40 + 24)
			{
			  /* Up button; is it available? */
			  
			  if (*thing_scroll > 0)
			    do_setcursor(cursor_up);
			  else
			    do_setcursor(cursor_arrow);
			}
		      else if (event.button.y > (48 * ((max - 2) / 2 + TOOLOFFSET / 2)) + 40 + 24 &&
		               event.button.y <= (48 * ((max - 2) / 2 + TOOLOFFSET / 2)) + 40 + 24 + 24)
			{
			  /* Down button; is it available? */
			  
			  if (*thing_scroll < num_things - (max - 2))
			    do_setcursor(cursor_down);
			  else
			    do_setcursor(cursor_arrow);
			}
		      else
			{
			  /* One of the selectors: */
			 
			  which = ((event.button.y - 40 - 24) / 48) * 2 +
			    (event.button.x - (WINDOW_WIDTH - 96)) / 48;

			  if (which < num_things)
			    do_setcursor(cursor_hand);
			  else
			    do_setcursor(cursor_arrow);
			}
		    }
		  else
		    {
		      /* No scroll buttons - must be a selector: */
		      
		      which = ((event.button.y - 40) / 48) * 2 +
			(event.button.x - (WINDOW_WIDTH - 96)) / 48;

		      if (which < num_things)
			do_setcursor(cursor_hand);
		      else
			do_setcursor(cursor_arrow);
		    }
		}
	      else if (HIT(r_canvas))
		{
		  /* Canvas: */
		  
		  if (cur_tool == TOOL_BRUSH)
		    do_setcursor(cursor_brush);
		  else if (cur_tool == TOOL_STAMP)
		    do_setcursor(cursor_tiny);
		  else if (cur_tool == TOOL_LINES)
		    do_setcursor(cursor_crosshair);
		  else if (cur_tool == TOOL_SHAPES)
		    {
		      if (shape_tool_mode != SHAPE_TOOL_MODE_ROTATE)
			do_setcursor(cursor_crosshair);
		      else
			do_setcursor(cursor_rotate);
		    }
		  else if (cur_tool == TOOL_TEXT)
		    do_setcursor(cursor_insertion);
		  else if (cur_tool == TOOL_MAGIC)
		    do_setcursor(cursor_wand);
		  else if (cur_tool == TOOL_ERASER)
		    do_setcursor(cursor_tiny);
		}
	      else
		{
		  do_setcursor(cursor_arrow);
		}
	      
	      
	      if (button_down)
		{
		  if (cur_tool == TOOL_BRUSH)
		    {
		      /* Pushing button and moving: Draw with the brush: */
		      
		      brush_draw(old_x, old_y, new_x, new_y, 1);
		      
		      playsound(0, SND_PAINT1 + (img_cur_brush->w) / 12, 0);
		    }
		  else if (cur_tool == TOOL_LINES)
		    {
		      /* Still pushing button, while moving:
			 Draw XOR where line will go: */
		      
		      line_xor(line_start_x, line_start_y, old_x, old_y);
		      
		      line_xor(line_start_x, line_start_y, new_x, new_y);
		      
		      update_screen(line_start_x + r_canvas.x, line_start_y + r_canvas.y,
				    old_x + r_canvas.x, old_y + r_canvas.y);
		      update_screen(line_start_x + r_canvas.x, line_start_y + r_canvas.y,
				    new_x + r_canvas.x, new_y + r_canvas.y);
		    }
		  else if (cur_tool == TOOL_SHAPES)
		    {
		      /* Still pushing button, while moving:
			 Draw XOR where shape will go: */
		      
		      if (shape_tool_mode == SHAPE_TOOL_MODE_STRETCH)
			{
			  do_shape(shape_ctr_x, shape_ctr_y, old_x, old_y,
				   0, 0);
			  
			  do_shape(shape_ctr_x, shape_ctr_y,
				   new_x, new_y,
				   0, 0);
			  
			  
			  /* FIXME: Fix update shape function! */
			  
			  /* update_shape(shape_ctr_x, old_x, new_x,
			     shape_ctr_y, old_y, new_y,
			     shape_locked[cur_shape]); */
			  
			  SDL_Flip(screen);
			}
		    }
		  else if (cur_tool == TOOL_MAGIC)
		    {
		      /* Pushing button and moving: Do the magic: */
		      
		      if (cur_magic != MAGIC_FLIP &&
			  cur_magic != MAGIC_MIRROR &&
			  cur_magic != MAGIC_FILL)
			{
			  magic_draw(old_x, old_y, new_x, new_y, button_down);
			}
		    }
		  else if (cur_tool == TOOL_ERASER)
		    {
		      /* Still pushing, and moving - Erase! */
		      
		      do_eraser(new_x, new_y);
		    }
		}
	      
	      
	      if (cur_tool == TOOL_STAMP ||
		  (cur_tool == TOOL_ERASER && !button_down))
		{
                  int w, h;
		  /* Moving: Draw XOR where stamp/eraser will apply: */
		  
		  if (cur_tool == TOOL_STAMP)
		    {
		      w = active_stamp->w;
		      h = active_stamp->h;
		    }
		  else
		    {
		      w = (ERASER_MIN +
			   ((NUM_ERASERS - cur_eraser - 1) *
			    ((ERASER_MAX - ERASER_MIN) / (NUM_ERASERS - 1))));

		      h = w;
		    }
		  
		  if (old_x >= 0 && old_x < r_canvas.w &&
		      old_y >= 0 && old_y < r_canvas.h)
		    {
		      if (cur_tool == TOOL_STAMP)
			{
			  stamp_xor(old_x, old_y);

			  update_screen(old_x - (CUR_STAMP_W+1)/2 + r_canvas.x,
					old_y - (CUR_STAMP_H+1)/2 + r_canvas.y,
					old_x + (CUR_STAMP_W+1)/2 + r_canvas.x,
					old_y + (CUR_STAMP_H+1)/2 + r_canvas.y);
			}
		      else
			{
			  rect_xor(old_x - w / 2, old_y - h / 2,
				   old_x + w / 2, old_y + h / 2);

			  update_screen(old_x - w / 2 + r_canvas.x, old_y - h / 2 + r_canvas.y,
					old_x + w / 2 + r_canvas.x, old_y + h / 2 + r_canvas.y);
			}
		    }
		  
		  if (new_x >= 0 && new_x < r_canvas.w &&
		      new_y >= 0 && new_y < r_canvas.h)
		    {
		      if (cur_tool == TOOL_STAMP)
			{
			  stamp_xor(new_x, new_y);

			  update_screen(old_x - (CUR_STAMP_W+1)/2 + r_canvas.x,
					old_y - (CUR_STAMP_H+1)/2 + r_canvas.y,
					old_x + (CUR_STAMP_W+1)/2 + r_canvas.x,
					old_y + (CUR_STAMP_H+1)/2 + r_canvas.y);
			}
		      else
			{
			  rect_xor(new_x - w / 2, new_y - h / 2,
				   new_x + w / 2, new_y + h / 2);

			  update_screen(new_x - w / 2 + r_canvas.x, new_y - h / 2 + r_canvas.y,
					new_x + w / 2 + r_canvas.x, new_y + h / 2 + r_canvas.y);
			}
		    }
		}
	      else if (cur_tool == TOOL_SHAPES &&
		       shape_tool_mode == SHAPE_TOOL_MODE_ROTATE)
		{
		  do_shape(shape_ctr_x, shape_ctr_y,
			   shape_outer_x, shape_outer_y,
			   rotation(shape_ctr_x, shape_ctr_y,
				    old_x, old_y), 0);
			      
		  
		  do_shape(shape_ctr_x, shape_ctr_y,
			   shape_outer_x, shape_outer_y,
			   rotation(shape_ctr_x, shape_ctr_y,
				    new_x, new_y), 0);
		  
		  
		  /* FIXME: Do something less intensive! */
		  SDL_Flip(screen);
		}
	      
	      old_x = new_x;
	      old_y = new_y;
	    }
	}


      SDL_Delay(10);
      
      cur_cursor_blink = SDL_GetTicks();
      
      
      if (cur_tool == TOOL_TEXT && cursor_x != -1 && cursor_y != -1 &&
	  cur_cursor_blink > last_cursor_blink + CURSOR_BLINK_SPEED)
	{
	  last_cursor_blink = SDL_GetTicks();
	  
	  line_xor(cursor_x + cursor_textwidth, cursor_y,
	           cursor_x + cursor_textwidth,
		   cursor_y + TTF_FontHeight(getfonthandle(cur_font)));

          update_screen(cursor_x + r_canvas.x + cursor_textwidth,
                        cursor_y + r_canvas.y,
			cursor_x + r_canvas.x + cursor_textwidth,
			cursor_y + r_canvas.y + TTF_FontHeight(getfonthandle(cur_font)));
	}
    }
  while (!done);
}


/* Draw using the current brush: */

static void brush_draw(int x1, int y1, int x2, int y2, int update)
{
  int dx, dy, y;
  int orig_x1, orig_y1, orig_x2, orig_y2, tmp;
  float m, b;

  orig_x1 = x1;
  orig_y1 = y1;

  orig_x2 = x2;
  orig_y2 = y2;


  x1 = x1 - (img_brushes[cur_brush]->w / 2);
  y1 = y1 - (img_brushes[cur_brush]->h / 2);

  x2 = x2 - (img_brushes[cur_brush]->w / 2);
  y2 = y2 - (img_brushes[cur_brush]->h / 2);


  dx = x2 - x1;
  dy = y2 - y1;

  if (dx != 0)
    {
      m = ((float) dy) / ((float) dx);
      b = y1 - m * x1;

      if (x2 >= x1)
	dx = 1;
      else
	dx = -1;


      while (x1 != x2)
	{
	  y1 = m * x1 + b;
	  y2 = m * (x1 + dx) + b;

	  if (y1 > y2)
	    {
	      y = y1;
	      y1 = y2;
	      y2 = y;
	    }
	
	  for (y = y1; y <= y2; y++)
	    blit_brush(x1, y);
	
	  x1 = x1 + dx;
	}
    }
  else
    {
      if (y1 > y2)
	{
	  y = y1;
	  y1 = y2;
	  y2 = y;
	}

      for (y = y1; y <= y2; y++)
	blit_brush(x1, y);
    }

  if (orig_x1 > orig_x2)
    {
      tmp = orig_x1;
      orig_x1 = orig_x2;
      orig_x2 = tmp;
    }

  if (orig_y1 > orig_y2)
    {
      tmp = orig_y1;
      orig_y1 = orig_y2;
      orig_y2 = tmp;
    }


  if (update)
    {
      update_canvas(orig_x1 - (img_brushes[cur_brush]->w / 2),
		    orig_y1 - (img_brushes[cur_brush]->h / 2),
		    orig_x2 + (img_brushes[cur_brush]->w / 2),
		    orig_y2 + (img_brushes[cur_brush]->h / 2));
    }
}


/* Draw the current brush in the current color: */

static void blit_brush(int x, int y)
{
  SDL_Rect dest;

  brush_counter++;

  if (brush_counter >= (img_cur_brush->h / 4))
    {
      brush_counter = 0;

      dest.x = x;
      dest.y = y;

      SDL_BlitSurface(img_cur_brush, NULL, canvas, &dest);
    }
}


//////////////////////////////////////////////////////////////////////////
// stamp tinter

#define TINTER_ANYHUE 0 // like normal, but remaps all hues in the stamp
#define TINTER_NARROW 1 // like normal, but narrow hue angle
#define TINTER_NORMAL 2 // normal
#define TINTER_VECTOR 3 // map black->white to black->destination

// This goes from 8-bit sRGB (range 0 to 255) to linear (range 0 to 1).
// The math to produce a table entry:
//    tmp = oldvalue / 255.0;
//    result = (tmp<=0.03928) ? tmp/12.92 : pow((tmp+0.055)/1.055,2.4);
static const float sRGB_to_linear_table[256] = {
  0.000000, 0.000304, 0.000607, 0.000911, 0.001214, 0.001518, 0.001821,
  0.002125, 0.002428, 0.002732, 0.003035, 0.003347, 0.003677, 0.004025,
  0.004391, 0.004777, 0.005182, 0.005605, 0.006049, 0.006512, 0.006995,
  0.007499, 0.008023, 0.008568, 0.009134, 0.009721, 0.010330, 0.010960,
  0.011612, 0.012286, 0.012983, 0.013702, 0.014444, 0.015209, 0.015996,
  0.016807, 0.017642, 0.018500, 0.019382, 0.020289, 0.021219, 0.022174,
  0.023153, 0.024158, 0.025187, 0.026241, 0.027321, 0.028426, 0.029557,
  0.030713, 0.031896, 0.033105, 0.034340, 0.035601, 0.036889, 0.038204,
  0.039546, 0.040915, 0.042311, 0.043735, 0.045186, 0.046665, 0.048172,
  0.049707, 0.051269, 0.052861, 0.054480, 0.056128, 0.057805, 0.059511,
  0.061246, 0.063010, 0.064803, 0.066626, 0.068478, 0.070360, 0.072272,
  0.074214, 0.076185, 0.078187, 0.080220, 0.082283, 0.084376, 0.086500,
  0.088656, 0.090842, 0.093059, 0.095307, 0.097587, 0.099899, 0.102242,
  0.104616, 0.107023, 0.109462, 0.111932, 0.114435, 0.116971, 0.119538,
  0.122139, 0.124772, 0.127438, 0.130136, 0.132868, 0.135633, 0.138432,
  0.141263, 0.144128, 0.147027, 0.149960, 0.152926, 0.155926, 0.158961,
  0.162029, 0.165132, 0.168269, 0.171441, 0.174647, 0.177888, 0.181164,
  0.184475, 0.187821, 0.191202, 0.194618, 0.198069, 0.201556, 0.205079,
  0.208637, 0.212231, 0.215861, 0.219526, 0.223228, 0.226966, 0.230740,
  0.234551, 0.238398, 0.242281, 0.246201, 0.250158, 0.254152, 0.258183,
  0.262251, 0.266356, 0.270498, 0.274677, 0.278894, 0.283149, 0.287441,
  0.291771, 0.296138, 0.300544, 0.304987, 0.309469, 0.313989, 0.318547,
  0.323143, 0.327778, 0.332452, 0.337164, 0.341914, 0.346704, 0.351533,
  0.356400, 0.361307, 0.366253, 0.371238, 0.376262, 0.381326, 0.386429,
  0.391572, 0.396755, 0.401978, 0.407240, 0.412543, 0.417885, 0.423268,
  0.428690, 0.434154, 0.439657, 0.445201, 0.450786, 0.456411, 0.462077,
  0.467784, 0.473531, 0.479320, 0.485150, 0.491021, 0.496933, 0.502886,
  0.508881, 0.514918, 0.520996, 0.527115, 0.533276, 0.539479, 0.545724,
  0.552011, 0.558340, 0.564712, 0.571125, 0.577580, 0.584078, 0.590619,
  0.597202, 0.603827, 0.610496, 0.617207, 0.623960, 0.630757, 0.637597,
  0.644480, 0.651406, 0.658375, 0.665387, 0.672443, 0.679542, 0.686685,
  0.693872, 0.701102, 0.708376, 0.715694, 0.723055, 0.730461, 0.737910,
  0.745404, 0.752942, 0.760525, 0.768151, 0.775822, 0.783538, 0.791298,
  0.799103, 0.806952, 0.814847, 0.822786, 0.830770, 0.838799, 0.846873,
  0.854993, 0.863157, 0.871367, 0.879622, 0.887923, 0.896269, 0.904661,
  0.913099, 0.921582, 0.930111, 0.938686, 0.947307, 0.955973, 0.964686,
  0.973445, 0.982251, 0.991102, 1.000000
};

// this goes the other way; range checking will be required
static const unsigned char linear_to_sRGB_table[4096] =
  "\x00\x01\x02\x03\x03\x04\x05\x06\x07\x08\x08\x09\x0a\x0b\x0b\x0c\x0d\x0d"
  "\x0e\x0f\x10\x10\x11\x11\x12\x12\x13\x13\x14\x14\x15\x15\x16\x16\x17\x17"
  "\x18\x18\x18\x19\x19\x1a\x1a\x1a\x1b\x1b\x1c\x1c\x1c\x1d\x1d\x1d\x1e\x1e"
  "\x1e\x1f\x1f\x1f\x20\x20\x20\x21\x21\x21\x22\x22\x22\x23\x23\x23\x23\x24"
  "\x24\x24\x25\x25\x25\x25\x26\x26\x26\x26\x27\x27\x27\x28\x28\x28\x28\x29"
  "\x29\x29\x29\x2a\x2a\x2a\x2a\x2b\x2b\x2b\x2b\x2c\x2c\x2c\x2c\x2c\x2d\x2d"
  "\x2d\x2d\x2e\x2e\x2e\x2e\x2f\x2f\x2f\x2f\x2f\x30\x30\x30\x30\x30\x31\x31"
  "\x31\x31\x31\x32\x32\x32\x32\x33\x33\x33\x33\x33\x34\x34\x34\x34\x34\x35"
  "\x35\x35\x35\x35\x35\x36\x36\x36\x36\x36\x37\x37\x37\x37\x37\x38\x38\x38"
  "\x38\x38\x38\x39\x39\x39\x39\x39\x39\x3a\x3a\x3a\x3a\x3a\x3a\x3b\x3b\x3b"
  "\x3b\x3b\x3c\x3c\x3c\x3c\x3c\x3c\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3e\x3e\x3e"
  "\x3e\x3e\x3e\x3f\x3f\x3f\x3f\x3f\x3f\x40\x40\x40\x40\x40\x40\x41\x41\x41"
  "\x41\x41\x41\x41\x42\x42\x42\x42\x42\x42\x42\x43\x43\x43\x43\x43\x43\x44"
  "\x44\x44\x44\x44\x44\x44\x45\x45\x45\x45\x45\x45\x45\x46\x46\x46\x46\x46"
  "\x46\x46\x46\x47\x47\x47\x47\x47\x47\x47\x48\x48\x48\x48\x48\x48\x48\x48"
  "\x49\x49\x49\x49\x49\x49\x49\x4a\x4a\x4a\x4a\x4a\x4a\x4a\x4a\x4b\x4b\x4b"
  "\x4b\x4b\x4b\x4b\x4c\x4c\x4c\x4c\x4c\x4c\x4c\x4c\x4d\x4d\x4d\x4d\x4d\x4d"
  "\x4d\x4d\x4e\x4e\x4e\x4e\x4e\x4e\x4e\x4e\x4f\x4f\x4f\x4f\x4f\x4f\x4f\x4f"
  "\x50\x50\x50\x50\x50\x50\x50\x50\x50\x51\x51\x51\x51\x51\x51\x51\x51\x51"
  "\x52\x52\x52\x52\x52\x52\x52\x52\x53\x53\x53\x53\x53\x53\x53\x53\x53\x54"
  "\x54\x54\x54\x54\x54\x54\x54\x54\x55\x55\x55\x55\x55\x55\x55\x55\x55\x56"
  "\x56\x56\x56\x56\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x58"
  "\x58\x58\x58\x58\x58\x58\x58\x58\x58\x59\x59\x59\x59\x59\x59\x59\x59\x59"
  "\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5b\x5b\x5b\x5b\x5b\x5b\x5b\x5b"
  "\x5b\x5b\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5c\x5d\x5d\x5d\x5d\x5d\x5d"
  "\x5d\x5d\x5d\x5e\x5e\x5e\x5e\x5e\x5e\x5e\x5e\x5e\x5e\x5e\x5f\x5f\x5f\x5f"
  "\x5f\x5f\x5f\x5f\x5f\x5f\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x61"
  "\x61\x61\x61\x61\x61\x61\x61\x61\x61\x62\x62\x62\x62\x62\x62\x62\x62\x62"
  "\x62\x62\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x63\x64\x64\x64\x64\x64"
  "\x64\x64\x64\x64\x64\x64\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x65\x66"
  "\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x67\x67\x67\x67\x67\x67\x67\x67"
  "\x67\x67\x67\x67\x68\x68\x68\x68\x68\x68\x68\x68\x68\x68\x68\x69\x69\x69"
  "\x69\x69\x69\x69\x69\x69\x69\x69\x6a\x6a\x6a\x6a\x6a\x6a\x6a\x6a\x6a\x6a"
  "\x6a\x6a\x6b\x6b\x6b\x6b\x6b\x6b\x6b\x6b\x6b\x6b\x6b\x6b\x6c\x6c\x6c\x6c"
  "\x6c\x6c\x6c\x6c\x6c\x6c\x6c\x6c\x6c\x6d\x6d\x6d\x6d\x6d\x6d\x6d\x6d\x6d"
  "\x6d\x6d\x6d\x6e\x6e\x6e\x6e\x6e\x6e\x6e\x6e\x6e\x6e\x6e\x6e\x6f\x6f\x6f"
  "\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x6f\x70\x70\x70\x70\x70\x70\x70\x70\x70"
  "\x70\x70\x70\x70\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x71\x72"
  "\x72\x72\x72\x72\x72\x72\x72\x72\x72\x72\x72\x72\x73\x73\x73\x73\x73\x73"
  "\x73\x73\x73\x73\x73\x73\x73\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74\x74"
  "\x74\x74\x75\x75\x75\x75\x75\x75\x75\x75\x75\x75\x75\x75\x75\x75\x76\x76"
  "\x76\x76\x76\x76\x76\x76\x76\x76\x76\x76\x76\x77\x77\x77\x77\x77\x77\x77"
  "\x77\x77\x77\x77\x77\x77\x77\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78"
  "\x78\x78\x78\x79\x79\x79\x79\x79\x79\x79\x79\x79\x79\x79\x79\x79\x79\x7a"
  "\x7a\x7a\x7a\x7a\x7a\x7a\x7a\x7a\x7a\x7a\x7a\x7a\x7b\x7b\x7b\x7b\x7b\x7b"
  "\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7c\x7c\x7c\x7c\x7c\x7c\x7c\x7c\x7c"
  "\x7c\x7c\x7c\x7c\x7c\x7d\x7d\x7d\x7d\x7d\x7d\x7d\x7d\x7d\x7d\x7d\x7d\x7d"
  "\x7d\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7f\x7f"
  "\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x7f\x80\x80\x80\x80\x80"
  "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x81\x81\x81\x81\x81\x81\x81\x81"
  "\x81\x81\x81\x81\x81\x81\x81\x82\x82\x82\x82\x82\x82\x82\x82\x82\x82\x82"
  "\x82\x82\x82\x82\x83\x83\x83\x83\x83\x83\x83\x83\x83\x83\x83\x83\x83\x83"
  "\x83\x83\x84\x84\x84\x84\x84\x84\x84\x84\x84\x84\x84\x84\x84\x84\x84\x85"
  "\x85\x85\x85\x85\x85\x85\x85\x85\x85\x85\x85\x85\x85\x85\x85\x86\x86\x86"
  "\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x87\x87\x87\x87\x87"
  "\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x87\x88\x88\x88\x88\x88\x88\x88"
  "\x88\x88\x88\x88\x88\x88\x88\x88\x88\x89\x89\x89\x89\x89\x89\x89\x89\x89"
  "\x89\x89\x89\x89\x89\x89\x89\x8a\x8a\x8a\x8a\x8a\x8a\x8a\x8a\x8a\x8a\x8a"
  "\x8a\x8a\x8a\x8a\x8a\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b\x8b"
  "\x8b\x8b\x8b\x8b\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c\x8c"
  "\x8c\x8c\x8c\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d\x8d"
  "\x8d\x8d\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e\x8e"
  "\x8e\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f\x8f"
  "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x91"
  "\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x91\x92"
  "\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x92\x93"
  "\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x93\x94\x94"
  "\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x94\x95\x95"
  "\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x95\x96\x96"
  "\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x96\x97"
  "\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x98"
  "\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98"
  "\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99"
  "\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a\x9a"
  "\x9a\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b\x9b"
  "\x9b\x9b\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c\x9c"
  "\x9c\x9c\x9c\x9c\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d\x9d"
  "\x9d\x9d\x9d\x9d\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
  "\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f"
  "\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0"
  "\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa0\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1"
  "\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa1\xa2\xa2\xa2\xa2\xa2\xa2"
  "\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa2\xa3\xa3\xa3\xa3"
  "\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa4"
  "\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4"
  "\xa4\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5\xa5"
  "\xa5\xa5\xa5\xa5\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6"
  "\xa6\xa6\xa6\xa6\xa6\xa6\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7"
  "\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa7\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8"
  "\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa8\xa9\xa9\xa9\xa9\xa9"
  "\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xa9\xaa\xaa"
  "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
  "\xaa\xaa\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab\xab"
  "\xab\xab\xab\xab\xab\xac\xac\xac\xac\xac\xac\xac\xac\xac\xac\xac\xac\xac"
  "\xac\xac\xac\xac\xac\xac\xac\xac\xac\xad\xad\xad\xad\xad\xad\xad\xad\xad"
  "\xad\xad\xad\xad\xad\xad\xad\xad\xad\xad\xad\xad\xad\xae\xae\xae\xae\xae"
  "\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xae\xaf"
  "\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf\xaf"
  "\xaf\xaf\xaf\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0"
  "\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"
  "\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xb2\xb2"
  "\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb2\xb3"
  "\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3\xb3"
  "\xb3\xb3\xb3\xb3\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4"
  "\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb4\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5"
  "\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb5\xb6\xb6\xb6"
  "\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6\xb6"
  "\xb6\xb6\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7"
  "\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb7\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8"
  "\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb8\xb9\xb9\xb9\xb9"
  "\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9\xb9"
  "\xb9\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba\xba"
  "\xba\xba\xba\xba\xba\xba\xba\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb"
  "\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbc\xbc\xbc\xbc"
  "\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc\xbc"
  "\xbc\xbc\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd"
  "\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbd\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe"
  "\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbe\xbf\xbf"
  "\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf\xbf"
  "\xbf\xbf\xbf\xbf\xbf\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0"
  "\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc1\xc1\xc1\xc1\xc1\xc1"
  "\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1\xc1"
  "\xc1\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2"
  "\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc2\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3"
  "\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc3\xc4\xc4"
  "\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4"
  "\xc4\xc4\xc4\xc4\xc4\xc4\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5"
  "\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc5\xc6\xc6\xc6\xc6"
  "\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6\xc6"
  "\xc6\xc6\xc6\xc6\xc6\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7"
  "\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc7\xc8\xc8\xc8\xc8\xc8"
  "\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8\xc8"
  "\xc8\xc8\xc8\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9"
  "\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xc9\xca\xca\xca\xca\xca\xca"
  "\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca\xca"
  "\xca\xca\xca\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb"
  "\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcb\xcc\xcc\xcc\xcc\xcc\xcc"
  "\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc"
  "\xcc\xcc\xcc\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
  "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xce\xce\xce\xce\xce"
  "\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce\xce"
  "\xce\xce\xce\xce\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf"
  "\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xcf\xd0\xd0\xd0\xd0"
  "\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0\xd0"
  "\xd0\xd0\xd0\xd0\xd0\xd0\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1"
  "\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd1\xd2\xd2"
  "\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2"
  "\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd2\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3"
  "\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3\xd3"
  "\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4"
  "\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd4\xd5\xd5\xd5\xd5\xd5\xd5\xd5"
  "\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5\xd5"
  "\xd5\xd5\xd5\xd5\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6"
  "\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd6\xd7\xd7\xd7"
  "\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7"
  "\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd7\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8"
  "\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8\xd8"
  "\xd8\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9"
  "\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xd9\xda\xda\xda\xda\xda"
  "\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda\xda"
  "\xda\xda\xda\xda\xda\xda\xda\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb"
  "\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb\xdb"
  "\xdb\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc"
  "\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdc\xdd\xdd\xdd\xdd\xdd"
  "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
  "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde"
  "\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde\xde"
  "\xde\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf"
  "\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xdf\xe0\xe0\xe0\xe0"
  "\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0"
  "\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe0\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1"
  "\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1\xe1"
  "\xe1\xe1\xe1\xe1\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2"
  "\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe2\xe3"
  "\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3"
  "\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe3\xe4\xe4\xe4\xe4\xe4\xe4"
  "\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4"
  "\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe4\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
  "\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
  "\xe5\xe5\xe5\xe5\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6"
  "\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe6\xe7"
  "\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7"
  "\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe7\xe8\xe8\xe8\xe8\xe8"
  "\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8"
  "\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe8\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9"
  "\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9\xe9"
  "\xe9\xe9\xe9\xe9\xe9\xe9\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea"
  "\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea"
  "\xea\xea\xea\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb"
  "\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xeb\xec"
  "\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec"
  "\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xec\xed\xed\xed"
  "\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed"
  "\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xed\xee\xee\xee\xee\xee\xee"
  "\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee"
  "\xee\xee\xee\xee\xee\xee\xee\xee\xee\xef\xef\xef\xef\xef\xef\xef\xef\xef"
  "\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef\xef"
  "\xef\xef\xef\xef\xef\xef\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0"
  "\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0"
  "\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1"
  "\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1\xf1"
  "\xf1\xf1\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2"
  "\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2\xf2"
  "\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3"
  "\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf3\xf4\xf4"
  "\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4"
  "\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf4\xf5\xf5\xf5"
  "\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5"
  "\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf5\xf6\xf6\xf6\xf6"
  "\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6"
  "\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf6\xf7\xf7\xf7\xf7\xf7\xf7"
  "\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7"
  "\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf7\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
  "\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8"
  "\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf8\xf9\xf9\xf9\xf9\xf9\xf9\xf9"
  "\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9"
  "\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xf9\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa"
  "\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa"
  "\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb"
  "\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb"
  "\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfb\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc"
  "\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc"
  "\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd"
  "\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd"
  "\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfd\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe"
  "\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe"
  "\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff"
  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
;

static unsigned char linear_to_sRGB (float linear) FUNCTION;
static unsigned char linear_to_sRGB (float linear)
{
  unsigned slot;
  slot = linear*4096.0 + 0.5;
  if(slot>4095)
    {
      if(linear>0.5)
        slot = 4095;
      else
        slot = 0;
    }
  return linear_to_sRGB_table[slot];
}

typedef struct multichan {
  double L,hue,sat; // L,a,b would be better -- 2-way formula unknown
  unsigned char or,og,ob,alpha;  // old 8-bit values
} multichan;

#define X0 ((double)0.9505)
#define Y0 ((double)1.0000)
#define Z0 ((double)1.0890)
#define u0_prime ( (4.0 * X0) / (X0 + 15.0*Y0 + 3.0*Z0) )
#define v0_prime ( (9.0 * Y0) / (X0 + 15.0*Y0 + 3.0*Z0) )


static void fill_multichan(multichan *mc, double *up, double *vp)
{
  double X,Y,Z,u,v;
  double u_prime, v_prime; /* temp, part of official formula */
  double Y_norm, fract; /* severely temp */

  double r = sRGB_to_linear_table[mc->or];
  double g = sRGB_to_linear_table[mc->og];
  double b = sRGB_to_linear_table[mc->ob];

  // coordinate change, RGB --> XYZ
  X = 0.4124*r + 0.3576*g + 0.1805*b;
  Y = 0.2126*r + 0.7152*g + 0.0722*b;
  Z = 0.0193*r + 0.1192*g + 0.9505*b;
  
  // XYZ --> Luv
  Y_norm = Y/Y0;
  fract = 1.0 / (X + 15.0*Y + 3.0*Z);
  u_prime = 4.0*X*fract;
  v_prime = 9.0*Y*fract;
  mc->L = (Y_norm>0.008856) ? 116.0*pow(Y_norm,1.0/3.0)-16.0 : 903.3*Y_norm;
  u = 13.0*mc->L*(u_prime - u0_prime);
  v = 13.0*mc->L*(v_prime - v0_prime);

  mc->sat = sqrt(u*u + v*v);
  mc->hue = atan2(u,v);
  if(up) *up = u;
  if(vp) *vp = v;
}


static double tint_part_1(multichan *work, SDL_Surface *in)
{
  int xx,yy;
  double u_total = 0;
  double v_total = 0;
  double u,v;
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[in->format->BytesPerPixel];
  

  SDL_LockSurface(in);
  for (yy = 0; yy < in->h; yy++)
    {
      for (xx = 0; xx < in->w; xx++)
        {
          multichan *mc = work+yy*in->w+xx;
          // put pixels into a more tolerable form
          SDL_GetRGBA(getpixel(in, xx, yy),
                      in->format,
                      &mc->or, &mc->og, &mc->ob, &mc->alpha);

          fill_multichan(mc,&u,&v);
          // average out u and v, giving more weight to opaque high-saturation pixels
          // (this is to take an initial guess at the primary hue)
          u_total += mc->alpha * u * mc->sat;
          v_total += mc->alpha * v * mc->sat;
        }
    }
  SDL_UnlockSurface(in);

  return atan2(u_total,v_total);
}


static void change_colors(SDL_Surface *out, multichan *work, double hue_range, multichan *key_color_ptr)
{
  double lower_hue_1,upper_hue_1,lower_hue_2,upper_hue_2;
  int xx,yy;
  multichan dst;
  double satratio;
  double slope;
  void (*putpixel)(SDL_Surface *, int, int, Uint32);
  double old_sat;
  double newsat;
  double L;
  double X,Y,Z;
  double u_prime, v_prime; /* temp, part of official formula */
  unsigned tries;
  double u;
  double v;
  double r, g, b;


  // prepare source and destination color info
  // should reset hue_range or not? won't bother for now
  multichan key_color = *key_color_ptr; // want to work from a copy, for safety
  lower_hue_1 = key_color.hue - hue_range;
  upper_hue_1 = key_color.hue + hue_range;
  if (lower_hue_1 < -M_PI)
    {
      lower_hue_2 = lower_hue_1 + 2 * M_PI;
      upper_hue_2 = upper_hue_1 + 2 * M_PI;
    }
  else
    {
      lower_hue_2 = lower_hue_1 - 2 * M_PI;
      upper_hue_2 = upper_hue_1 - 2 * M_PI;
    }

  // get the destination color set up
  dst.or = color_hexes[cur_color][0];
  dst.og = color_hexes[cur_color][1];
  dst.ob = color_hexes[cur_color][2];
  fill_multichan(&dst,NULL,NULL);

  satratio = dst.sat / key_color.sat;
  slope = (dst.L-key_color.L)/dst.sat;
  putpixel = putpixels[out->format->BytesPerPixel];

  SDL_LockSurface(out);
  for (yy = 0; yy < out->h; yy++)
    {
      for (xx = 0; xx < out->w; xx++)
	{
	  multichan *mc = work+yy*out->w+xx;
          double oldhue = mc->hue;

          // if not in the first range, and not in the second range, skip this one
          // (really should alpha-blend as a function of hue angle difference)
          if( (oldhue<lower_hue_1 || oldhue>upper_hue_1) && (oldhue<lower_hue_2 || oldhue>upper_hue_2) )
            {
              putpixel(out, xx, yy, SDL_MapRGBA(out->format, mc->or, mc->og, mc->ob, mc->alpha));
	      continue;
	    }

          // Modify the pixel
          old_sat = mc->sat;
          newsat = old_sat * satratio;
          L = mc->L;
          if(dst.sat>0)
	    L  += newsat * slope; // not greyscale destination
          else
	    L  += old_sat*(dst.L-key_color.L)/key_color.sat;

          // convert from L,u,v all the way back to sRGB with 8-bit channels
          tries = 3;
trysat:;
          u = newsat * sin(dst.hue);
          v = newsat * cos(dst.hue);

          // Luv to XYZ
          u_prime = u/(13.0*L)+u0_prime;
          v_prime = v/(13.0*L)+v0_prime;
          Y = (L>7.99959199307) ? Y0*pow((L+16.0)/116.0,3.0) : Y0*L/903.3;
          X = 2.25*Y*u_prime/v_prime;
          Z = (3.0*Y - 0.75*Y*u_prime)/v_prime - 5.0*Y;
          
          // coordinate change: XYZ to RGB
          r =  3.2410*X + -1.5374*Y + -0.4986*Z;
          g = -0.9692*X +  1.8760*Y +  0.0416*Z;
          b =  0.0556*X + -0.2040*Y +  1.0570*Z;
          
          // If it is out of gamut, try to de-saturate it a few times before truncating.
          // (the linear_to_sRGB function will truncate)
          if((r<=-0.5 || g<=-0.5 || b<=-0.5 || r>=255.5 || g>=255.5 || b>=255.5) && tries--)
            {
              newsat *= 0.8;
              goto trysat;
            }

	  putpixel(out, xx, yy,
		   SDL_MapRGBA(out->format, linear_to_sRGB(r), linear_to_sRGB(g), linear_to_sRGB(b), mc->alpha));
	}
    }
  SDL_UnlockSurface(out);
}


static multichan *find_most_saturated(double initial_hue, multichan * work,
				      unsigned num, double * hue_range_ptr)
{
  // find the most saturated pixel near the initial hue guess
  multichan *key_color_ptr = NULL;
  double hue_range;
  unsigned i;
  double max_sat;
  double lower_hue_1;
  double upper_hue_1;
  double lower_hue_2;
  double upper_hue_2;
  multichan * mc;

  switch (stamp_data[cur_stamp]->tinter)
  {
    default:
    case TINTER_NORMAL:
      hue_range = 18*M_PI/180.0; // plus or minus 18 degrees search, 27 replace
      break;
    case TINTER_NARROW:
      hue_range = 6*M_PI/180.0;  // plus or minus 6 degrees search, 9 replace
      break;
    case TINTER_ANYHUE:
      hue_range = M_PI;          // plus or minus 180 degrees
      break;
  }

hue_range_retry:;

  max_sat = 0;
  lower_hue_1 = initial_hue - hue_range;
  upper_hue_1 = initial_hue + hue_range;
  
  if (lower_hue_1 < -M_PI)
    {
      lower_hue_2 = lower_hue_1 + 2 * M_PI;
      upper_hue_2 = upper_hue_1 + 2 * M_PI;
    }
  else
    {
      lower_hue_2 = lower_hue_1 - 2 * M_PI;
      upper_hue_2 = upper_hue_1 - 2 * M_PI;
    }
  
  i = num;
  while (i--)
    {
      mc = work + i;

      // if not in the first range, and not in the second range, skip this one
      // 
      if ((mc->hue<lower_hue_1 || mc->hue>upper_hue_1) &&
	  (mc->hue<lower_hue_2 || mc->hue>upper_hue_2))
	continue;
      
      if(mc->sat > max_sat)
      {
	max_sat = mc->sat;
	key_color_ptr = mc;
      }
    }
  
  if (!key_color_ptr)
    {
      hue_range *= 1.5;
      
      if (hue_range < M_PI)
	goto hue_range_retry;
    }
  
  *hue_range_ptr = hue_range;
  
  return key_color_ptr;
}


static void vector_tint_surface(SDL_Surface * out, SDL_Surface * in)
{
  int xx,yy;
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[in->format->BytesPerPixel];
  void (*putpixel)(SDL_Surface *, int, int, Uint32) = putpixels[out->format->BytesPerPixel];

  double r = sRGB_to_linear_table[color_hexes[cur_color][0]];
  double g = sRGB_to_linear_table[color_hexes[cur_color][1]];
  double b = sRGB_to_linear_table[color_hexes[cur_color][2]];

  SDL_LockSurface(in);
  for (yy = 0; yy < in->h; yy++)
    {
      for (xx = 0; xx < in->w; xx++)
        {
          unsigned char r8, g8, b8, a8;
	  double old;
	  
          SDL_GetRGBA(getpixel(in, xx, yy),
                      in->format,
                      &r8, &g8, &b8, &a8);
          // get the linear greyscale value
          old = sRGB_to_linear_table[r8]*0.2126 + sRGB_to_linear_table[g8]*0.7152 + sRGB_to_linear_table[b8]*0.0722;

	  putpixel(out, xx, yy,
		   SDL_MapRGBA(out->format, linear_to_sRGB(r*old), linear_to_sRGB(g*old), linear_to_sRGB(b*old), a8));
        }
    }
  SDL_UnlockSurface(in);
}


static void tint_surface(SDL_Surface * tmp_surf, SDL_Surface * surf_ptr)
{
  unsigned width = surf_ptr->w;
  unsigned height = surf_ptr->h;
  multichan * work;
  multichan * key_color_ptr;
  double initial_hue;
  double hue_range;
  
 
  work = malloc(sizeof(multichan) * width * height);

  if (work)
  {
    initial_hue = tint_part_1(work, surf_ptr);

    key_color_ptr = find_most_saturated(initial_hue, work,
		    			width * height, &hue_range);

    if (key_color_ptr)
    {
      // wider for processing than for searching
      hue_range *= 1.5;
      
      change_colors(tmp_surf, work, hue_range, key_color_ptr);
    
      free(work);
      return;
    }
  
    free(work);
  }

  /* Failed!  Fall back: */

  //fprintf(stderr, "Falling back to tinter=vector, "
  //		    "this should be in the *.dat file\n");
  
  vector_tint_surface(tmp_surf, surf_ptr);
}


//////////////////////////////////////////////////////////////////////

/* Draw using the current stamp: */

static void stamp_draw(int x, int y)
{
  SDL_Rect dest;
  SDL_Surface * tmp_surf, * surf_ptr, * final_surf;
  Uint32 amask;
  Uint8 r, g, b, a;
  int xx, yy, dont_free_tmp_surf, base_x, base_y;
  Uint32 (*getpixel)(SDL_Surface *, int, int);
  void (*putpixel)(SDL_Surface *, int, int, Uint32);
  
  surf_ptr = active_stamp;
  
  getpixel = getpixels[surf_ptr->format->BytesPerPixel];


  /* Create a temp surface to play with: */
  
  if (stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp))
    {
      amask = ~(surf_ptr->format->Rmask |
		surf_ptr->format->Gmask |
		surf_ptr->format->Bmask);

      tmp_surf =
	SDL_CreateRGBSurface(SDL_SWSURFACE,
			     surf_ptr->w,
			     surf_ptr->h,
			     surf_ptr->format->BitsPerPixel,
			     surf_ptr->format->Rmask,
			     surf_ptr->format->Gmask,
			     surf_ptr->format->Bmask,
			     amask);

      if (tmp_surf == NULL)
	{
	  fprintf(stderr, "\nError: Can't render the colored stamp!\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", SDL_GetError());

	  cleanup();
	  exit(1);
	}

      dont_free_tmp_surf = 0;
    }
  else
    {
      /* Not altering color; no need to create temp surf if we don't use it! */

      tmp_surf = NULL;
      dont_free_tmp_surf = 1;
    }

  if (tmp_surf != NULL)
    putpixel = putpixels[tmp_surf->format->BytesPerPixel];
  else
    putpixel = NULL;


  /* Alter the stamp's color, if needed: */

  if (stamp_colorable(cur_stamp) && tmp_surf != NULL)
    {
      /* Render the stamp in the chosen color: */

      /* FIXME: It sucks to render this EVERY TIME.  Why not just when
	 they pick the color, or pick the stamp, like with brushes? */

      /* Render the stamp: */

      SDL_LockSurface(surf_ptr);
      SDL_LockSurface(tmp_surf);

      for (yy = 0; yy < surf_ptr->h; yy++)
	{
	  for (xx = 0; xx < surf_ptr->w; xx++)
	    {
	      SDL_GetRGBA(getpixel(surf_ptr, xx, yy),
			  surf_ptr->format,
			  &r, &g, &b, &a);
	
	      putpixel(tmp_surf, xx, yy,
		       SDL_MapRGBA(tmp_surf->format,
				   color_hexes[cur_color][0],
				   color_hexes[cur_color][1],
				   color_hexes[cur_color][2],
				   a));
	    }
	}

      SDL_UnlockSurface(tmp_surf);
      SDL_UnlockSurface(surf_ptr);
    }
  else if (stamp_tintable(cur_stamp))
    {
      if (stamp_data[cur_stamp]->tinter == TINTER_VECTOR)
        vector_tint_surface(tmp_surf, surf_ptr);
      else
        tint_surface(tmp_surf, surf_ptr);
    }
  else
    {
      /* No color change, just use it! */
      tmp_surf = surf_ptr;
    }

  /* Shrink or grow it! */
  final_surf = thumbnail(tmp_surf, CUR_STAMP_W, CUR_STAMP_H, 0);
  
  /* Where it will go? */
  base_x = x - (CUR_STAMP_W+1)/2;
  base_y = y - (CUR_STAMP_H+1)/2;

  /* And blit it! */
  dest.x = base_x;
  dest.y = base_y;
  SDL_BlitSurface(final_surf, NULL, canvas, &dest);

  update_canvas(x - (CUR_STAMP_W+1)/2,
		y - (CUR_STAMP_H+1)/2,
                x + (CUR_STAMP_W+1)/2,
		y + (CUR_STAMP_H+1)/2);
  
  /* Free the temporary surfaces */

  if (!dont_free_tmp_surf)
    SDL_FreeSurface(tmp_surf);
  
  SDL_FreeSurface(final_surf);
}


/* Draw using the current brush: */

static void magic_draw(int x1, int y1, int x2, int y2, int button_down)
{
  int dx, dy, y;
  int orig_x1, orig_y1, orig_x2, orig_y2, tmp;
  float m, b;

  if (cur_magic == MAGIC_RAINBOW)
    rainbow_color = (rainbow_color + 1) % NUM_RAINBOW_COLORS;

  orig_x1 = x1;
  orig_y1 = y1;

  orig_x2 = x2;
  orig_y2 = y2;


  dx = x2 - x1;
  dy = y2 - y1;

  if (dx != 0)
    {
      m = ((float) dy) / ((float) dx);
      b = y1 - m * x1;

      if (x2 >= x1)
	dx = 1;
      else
	dx = -1;


      while (x1 != x2)
	{
	  y1 = m * x1 + b;
	  y2 = m * (x1 + dx) + b;

          if (y1 > y2)
	    {
	      for (y = y1; y >= y2; y--)
		blit_magic(x1, y, button_down);
	    }
          else
	    {
	      for (y = y1; y <= y2; y++)
		blit_magic(x1, y, button_down);
	    }
	
	  x1 = x1 + dx;
	}
    }
  else
    {
      if (y1 > y2)
	{
	  for (y = y1; y >= y2; y--)
	    blit_magic(x1, y, button_down);
	}
      else
	{
	  for (y = y1; y <= y2; y++)
	    blit_magic(x1, y, button_down);
	}
    }

  if (orig_x1 > orig_x2)
    {
      tmp = orig_x1;
      orig_x1 = orig_x2;
      orig_x2 = tmp;
    }

  if (orig_y1 > orig_y2)
    {
      tmp = orig_y1;
      orig_y1 = orig_y2;
      orig_y2 = tmp;
    }


  /* Play sound: */

  if (cur_magic == MAGIC_DRIP)
    playsound(0, SND_DRIP, 0);
  else if (cur_magic == MAGIC_CHALK)
    playsound(0, SND_CHALK, 0);
  else if (cur_magic == MAGIC_SPARKLES)
    playsound(0, SND_SPARKLES1 + (rand() % 2), 0);
  else if (cur_magic == MAGIC_FLIP)
    playsound(0, SND_FLIP, 0);
  else if (cur_magic == MAGIC_MIRROR)
    playsound(0, SND_MIRROR, 0);
  else if (cur_magic == MAGIC_NEGATIVE)
    playsound(0, SND_NEGATIVE, 0);
  else if (cur_magic == MAGIC_BLUR)
    playsound(0, SND_BLUR, 0);
  else if (cur_magic == MAGIC_BLOCKS && ((rand() % 10) < 5))
    playsound(0, SND_BLOCKS, 0);
  else if (cur_magic == MAGIC_FADE)
    playsound(0, SND_FADE, 0);
  else if (cur_magic == MAGIC_DARKEN)
    playsound(0, SND_DARKEN, 0);
  else if (cur_magic == MAGIC_RAINBOW)
    playsound(0, SND_RAINBOW, 0);
  else if (cur_magic == MAGIC_SMUDGE)
    playsound(0, SND_SMUDGE, 0);
  else if (cur_magic == MAGIC_CARTOON)
    playsound(0, SND_CARTOON, 0);
  else if (cur_magic == MAGIC_TINT)
    playsound(0, SND_TINT, 0);

  /* FIXME: Need sounds for:
     Grass (mower?) */


  /* FIXME: Arbitrary? */

  update_canvas(orig_x1 - 32, orig_y1 - 32,
		orig_x2 + 32, orig_y2 + 32);
}


// this one rounds down
static int log2int(int x)
{
  int y = 0;
  if(x<=1)
    return 0;
  x >>= 1;
  while(x)
    {
      x >>= 1;
      y++;
    }
  return y;
}

static void do_brick(int x, int y, int w, int h)
{
  SDL_Rect dest;

  // brick color: 127,76,73
  double ran_r = rand()/(double)RAND_MAX;
  double ran_g = rand()/(double)RAND_MAX;
  double base_r = sRGB_to_linear_table[color_hexes[cur_color][0]]*1.5 + sRGB_to_linear_table[127]*5.0 + ran_r;
  double base_g = sRGB_to_linear_table[color_hexes[cur_color][1]]*1.5 + sRGB_to_linear_table[76] *5.0 + ran_g;
  double base_b = sRGB_to_linear_table[color_hexes[cur_color][2]]*1.5 + sRGB_to_linear_table[73] *5.0 + (ran_r+ran_g*2.0)/3.0;

  Uint8 r = linear_to_sRGB(base_r/7.5);
  Uint8 g = linear_to_sRGB(base_g/7.5);
  Uint8 b = linear_to_sRGB(base_b/7.5);

  dest.x = x;
  dest.y = y;
  dest.w = w;
  dest.h = h;


  SDL_FillRect(canvas, &dest, SDL_MapRGB(canvas->format, r, g, b));

// This is better for debugging brick layout:
//  SDL_FillRect(canvas, &dest, SDL_MapRGB(canvas->format, rand()&255, rand()&255, rand()&255));
  
  
  /* Note: We only play the brick sound when we actually DRAW a brick: */

  playsound(0, SND_BRICK, 1);
}

/* Draw the current brush in the current color: */

static void blit_magic(int x, int y, int button_down)
{
  int xx, yy, w, h;
  Uint32 colr;
  Uint8 r, g, b, a;
  SDL_Surface * last;
  SDL_Rect src, dest;
  int undo_ctr;
  Uint32 (*getpixel_canvas)(SDL_Surface *, int, int) = getpixels[canvas->format->BytesPerPixel];
  void (*putpixel)(SDL_Surface *, int, int, Uint32) = putpixels[canvas->format->BytesPerPixel];
  Uint32 (*getpixel_last)(SDL_Surface *, int, int);


  /* In case we need to use the current canvas (just saved to undo buf)... */

  if (cur_undo > 0)
    undo_ctr = cur_undo - 1;
  else
    undo_ctr = NUM_UNDO_BUFS - 1;

  last = undo_bufs[undo_ctr];
  getpixel_last = getpixels[last->format->BytesPerPixel];


  brush_counter++;

  if (brush_counter >= 4)  /* FIXME: Arbitrary? */
    {
      brush_counter = 0;


      if (cur_magic == MAGIC_BLUR)
	{
	  double state[32][32][3];
	  unsigned i = 32*32;

	  SDL_LockSurface(canvas);

	  while (i--)
	    {
	      int iy = i>>5;
	      int ix = i&0x1f;
	      // is it not on the circle of radius sqrt(220) at location 16,16?
	      if ( (ix-16)*(ix-16) + (iy-16)*(iy-16) > 220)
	        continue;
	      // it is on the circle, so grab it

	      SDL_GetRGB(getpixel_canvas(canvas, x+ix-16, y+iy-16), last->format, &r, &g, &b);
	      state[ix][iy][0] = sRGB_to_linear_table[r];
	      state[ix][iy][1] = sRGB_to_linear_table[g];
	      state[ix][iy][2] = sRGB_to_linear_table[b];
	    }
	  i = 32*32;
	  while (i--)
	    {
	      double lr, lg, lb;  // linear red,green,blue
	      double weight;
	      int iy = i>>5;
	      int ix = i&0x1f;
	      int r2 = (ix-16)*(ix-16) + (iy-16)*(iy-16); // radius squared

	      // is it not on the circle of radius sqrt(140) at location 16,16?
	      if ( r2 > 140)
	        continue;

	      // It is on the circle, but how strongly will it be affected?
	      // This is lame; we should use something like a gaussian or cosine
	      // via a lookup table. (inverted, because this is the center weight)
	      weight = r2/16.0+3.0;

	      // Sampling more points would be good too, though it'd be slower.

	      lr =                     state[ix][iy-1][0]
	        + state[ix-1][iy][0] + state[ix][iy  ][0]*weight + state[ix+1][iy][0]
	                             + state[ix][iy+1][0];

	      lg =                     state[ix][iy-1][1]
	        + state[ix-1][iy][1] + state[ix][iy  ][1]*weight + state[ix+1][iy][1]
	                             + state[ix][iy+1][1];

	      lb =                     state[ix][iy-1][2]
	        + state[ix-1][iy][2] + state[ix][iy  ][2]*weight + state[ix+1][iy][2]
	                             + state[ix][iy+1][2];

              lr /= weight+4.0;
              lg /= weight+4.0;
              lb /= weight+4.0;
	      putpixel(canvas, x+ix-16, y+iy-16, SDL_MapRGB(canvas->format, linear_to_sRGB(lr), linear_to_sRGB(lg), linear_to_sRGB(lb)));
	    }
	  SDL_UnlockSurface(canvas);
	}
      else if (cur_magic == MAGIC_BLOCKS)
	{
	  /* Put x/y on exact grid points: */

	  x = (x / 4) * 4;
	  y = (y / 4) * 4;

	  SDL_LockSurface(last);
	  SDL_LockSurface(canvas);

	  for (yy = y - 8; yy < y + 8; yy = yy + 4)
	    {
	      for (xx = x - 8; xx < x + 8; xx = xx + 4)
		{
		  Uint32 pix[16];
		  Uint32 p_or = 0;
		  Uint32 p_and = ~0;
		  unsigned i = 16;
		  while(i--)
		    {
		      Uint32 p_tmp;
		      p_tmp = getpixel_last(last, xx+(i>>2), yy+(i&3));
		      p_or |= p_tmp;
		      p_and &= p_tmp;
		      pix[i] = p_tmp;
		    }
		  if(p_or==p_and)  // if all pixels the same already
		    {
		      SDL_GetRGB(p_or, last->format, &r, &g, &b);
		    }
		  else  // nope, must average them
		    {
		      double r_sum = 0.0;
		      double g_sum = 0.0;
		      double b_sum = 0.0;
		      i = 16;
		      while(i--)
		        {
		          SDL_GetRGB(pix[i], last->format, &r, &g, &b);
		          r_sum += sRGB_to_linear_table[r];
		          g_sum += sRGB_to_linear_table[g];
		          b_sum += sRGB_to_linear_table[b];
		        }
		      r = linear_to_sRGB(r_sum/16.0);
		      g = linear_to_sRGB(g_sum/16.0);
		      b = linear_to_sRGB(b_sum/16.0);
		    }
	
		  /* Draw block: */

		  dest.x = xx;
		  dest.y = yy;
		  dest.w = 4;
		  dest.h = 4;

		  SDL_FillRect(canvas, &dest, SDL_MapRGB(canvas->format, r, g, b));
		}
	    }
	
	  SDL_UnlockSurface(canvas);
	  SDL_UnlockSurface(last);
	}
      else if (cur_magic == MAGIC_LARGEBRICK || cur_magic == MAGIC_SMALLBRICK)
	{
	  // "specified" means the brick itself, w/o morter
	  // "nominal" means brick-to-brick (includes morter)
	  int specified_width, specified_height, specified_length;
	  int nominal_length;
	  int brick_x, brick_y;

	  int vertical_joint   = 2; // between a brick and the one above/below
	  int horizontal_joint = 2; // between a brick and the one to the side
	  int nominal_width    = 18;
	  int nominal_height   = 12; // 11 to 14, for joints of 2
	  static unsigned char *map;
	  static int x_count;
	  static int y_count;
          unsigned char *mybrick;

#if 0
	  if (cur_magic == MAGIC_SMALLBRICK)
	    {
	      vertical_joint   = 1; // between a brick and the one above/below
	      horizontal_joint = 1; // between a brick and the one to the side
	      nominal_width    = 9;
	      nominal_height   = 6; // 11 to 14, for joints of 2
	    }
#endif
#if 0
	  if (cur_magic == MAGIC_LARGEBRICK)
	    {
	      vertical_joint   = 3; // between a brick and the one above/below
	      horizontal_joint = 3; // between a brick and the one to the side
	      nominal_width    = 27;
	      nominal_height   = 18; // 11 to 14, for joints of 2
	    }
#endif
#if 1
	  if (cur_magic == MAGIC_LARGEBRICK)
	    {
	      vertical_joint   = 4; // between a brick and the one above/below
	      horizontal_joint = 4; // between a brick and the one to the side
	      nominal_width    = 36;
	      nominal_height   = 24; // 11 to 14, for joints of 2
	    }
#endif

          nominal_length   = 2 * nominal_width;
	  specified_width  = nominal_width - horizontal_joint;
	  specified_height = nominal_height - vertical_joint;
	  specified_length = nominal_length - horizontal_joint;

          if (!button_down)
            {
              if (map)
                free(map);
              // the "+ 3" allows for both ends and misalignment
              x_count = (canvas->w + nominal_width  - 1) / nominal_width + 3;
              y_count = (canvas->h + nominal_height - 1) / nominal_height + 3;
              map = calloc(x_count,y_count);
            }

	  brick_x = x / nominal_width;
	  brick_y = y / nominal_height;

          mybrick = map + brick_x+1 + (brick_y+1)*x_count;

          if ( (unsigned)x < (unsigned)canvas->w && (unsigned)y < (unsigned)canvas->h && !*mybrick)
            {
              int my_x = brick_x*nominal_width;
              int my_w = specified_width;
              *mybrick = 1;
              SDL_LockSurface(canvas);
              if((brick_y^brick_x)&1)
                {
                  if(mybrick[1])
                    my_w = specified_length;
                }
              else
                if(mybrick[-1])
                  {
                    my_x -= nominal_width;
                    my_w = specified_length;
                  }
              do_brick(my_x, brick_y*nominal_height, my_w,  specified_height);
              SDL_UnlockSurface(canvas);
              //            upper left corner        and     lower right corner
              update_canvas(brick_x*nominal_width-nominal_width, brick_y*nominal_height-vertical_joint, brick_x*nominal_width+specified_length,  (brick_y+1)*nominal_height);
            }
	}
      else if (cur_magic == MAGIC_SMUDGE)
	{
	  static double state[32][32][3];
	  unsigned i = 32*32;
	  double rate = button_down ? 0.5 : 0.0;

	  SDL_LockSurface(canvas);

	  while (i--)
	    {
	      int iy = i>>5;
	      int ix = i&0x1f;
	      // is it not on the circle of radius sqrt(120) at location 16,16?
	      if ( (ix-16)*(ix-16) + (iy-16)*(iy-16) > 120)
	        continue;
	      // it is on the circle, so grab it

	      SDL_GetRGB(getpixel_canvas(canvas, x+ix-16, y+iy-16), last->format, &r, &g, &b);
	      state[ix][iy][0] = rate*state[ix][iy][0] + (1.0-rate)*sRGB_to_linear_table[r];
	      state[ix][iy][1] = rate*state[ix][iy][1] + (1.0-rate)*sRGB_to_linear_table[g];
	      state[ix][iy][2] = rate*state[ix][iy][2] + (1.0-rate)*sRGB_to_linear_table[b];

	      // opacity 100% --> new data not blended w/ existing data
	      putpixel(canvas, x+ix-16, y+iy-16, SDL_MapRGB(canvas->format, linear_to_sRGB(state[ix][iy][0]), linear_to_sRGB(state[ix][iy][1]), linear_to_sRGB(state[ix][iy][2])));
	    }
	  SDL_UnlockSurface(canvas);
	}
      else if (cur_magic == MAGIC_NEGATIVE)
	{
	  SDL_LockSurface(last);
	  SDL_LockSurface(canvas);

	  for (yy = y - 16; yy < y + 16; yy++)
	    {
	      for (xx = x - 16; xx < x + 16; xx++)
		{
		  SDL_GetRGB(getpixel_last(last, xx, yy), last->format,
			     &r, &g, &b);
	
		  r = 0xFF - r;
		  g = 0xFF - g;
		  b = 0xFF - b;

		  putpixel(canvas, xx, yy, SDL_MapRGB(canvas->format, r, g, b));
		}
	    }

	  SDL_UnlockSurface(canvas);
	  SDL_UnlockSurface(last);
	}
      else if (cur_magic == MAGIC_FADE || cur_magic == MAGIC_DARKEN)
	{
	  SDL_LockSurface(last);
	  SDL_LockSurface(canvas);

	  for (yy = y - 16; yy < y + 16; yy++)
	    {
	      for (xx = x - 16; xx < x + 16; xx++)
		{
		  /* Get original color: */
		
		  SDL_GetRGB(getpixel_last(last, xx, yy), last->format,
			     &r, &g, &b);

		  if (cur_magic == MAGIC_FADE)
		  {
		    r = min(r + 48, 255);
		    g = min(g + 48, 255);
		    b = min(b + 48, 255);
		  }
		  else
		  {
		    r = max(r - 48, 0);
		    g = max(g - 48, 0);
		    b = max(b - 48, 0);
		  }

		  putpixel(canvas, xx, yy, SDL_MapRGB(canvas->format, r, g, b));
		}
	    }

	  SDL_UnlockSurface(canvas);
	  SDL_UnlockSurface(last);
	}
      else if (cur_magic == MAGIC_TINT)
	{
	  double rd = sRGB_to_linear_table[color_hexes[cur_color][0]];
	  double gd = sRGB_to_linear_table[color_hexes[cur_color][1]];
	  double bd = sRGB_to_linear_table[color_hexes[cur_color][2]];
	  double old;

	  SDL_LockSurface(last);
	  SDL_LockSurface(canvas);

	  for (yy = y - 16; yy < y + 16; yy++)
	    {
	      for (xx = x - 16; xx < x + 16; xx++)
		{
		  /* Get original pixel: */
		
		  SDL_GetRGB(getpixel_last(last, xx, yy), last->format,
			     &r, &g, &b);
  
                  old = sRGB_to_linear_table[r] * 0.2126 +
			sRGB_to_linear_table[g] * 0.7152 +
			sRGB_to_linear_table[b] * 0.0722;

		  putpixel(canvas, xx, yy,
			   SDL_MapRGB(canvas->format,
			              linear_to_sRGB(rd * old),
				      linear_to_sRGB(gd * old),
				      linear_to_sRGB(bd * old)));
		}
	    }

	  SDL_UnlockSurface(canvas);
	  SDL_UnlockSurface(last);
	}
      else if (cur_magic == MAGIC_CARTOON)
	{
	  float hue, sat, val;
	  Uint8 r1, g1, b1, r2, g2, b2;
          Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[last->format->BytesPerPixel];
	  
	  SDL_LockSurface(last);
	  SDL_LockSurface(canvas);

	  /* First, convert colors to more cartoony ones: */

	  for (yy = y - 16; yy < y + 16; yy = yy + 1)
	    {
	      for (xx = x - 16; xx < x + 16; xx = xx + 1)
		{
		  /* Get original color: */
	
			/*
		  SDL_GetRGB(getpixel_last(last, xx, yy),
		             last->format, &r1, &g1, &b1);
		  SDL_GetRGB(getpixel_last(last, xx + 1, yy),
		             last->format, &r2, &g2, &b2);
		  SDL_GetRGB(getpixel_last(last, xx, yy + 1),
		             last->format, &r3, &g3, &b3);
		  SDL_GetRGB(getpixel_last(last, xx + 1, yy + 1),
		             last->format, &r4, &g4, &b4);

		  r = (r1 + r2 + r3 + r4) / 4;
		  g = (g1 + g2 + g3 + g4) / 4;
		  b = (b1 + b2 + b3 + b4) / 4;
*/
		  SDL_GetRGB(getpixel_last(last, xx, yy),
		             last->format, &r, &g, &b);

		  rgbtohsv(r, g, b, &hue, &sat, &val);

		  /*
		  if (sat <= 0.2)
		    sat = 0.0;
		  else if (sat <= 0.7)
		    sat = 0.5;
		  else
		    sat = 1.0;
		*/

		  val = val - 0.5;
		  val = val * 4;
		  val = val + 0.5;

		  if (val < 0)
		    val = 0;
		  else if (val > 1.0)
		    val = 1.0;

		  val = floor(val * 4) / 4;
		  hue = floor(hue * 4) / 4;

		  sat = floor(sat * 4) / 4;

		  hsvtorgb(hue, sat, val, &r, &g, &b);

		  putpixel(canvas, xx, yy,
		           SDL_MapRGB(canvas->format, r, g, b));
		  /*
		  putpixel(canvas, xx + 1, yy,
		           SDL_MapRGB(canvas->format, r, g, b));
		  putpixel(canvas, xx, yy + 1,
		           SDL_MapRGB(canvas->format, r, g, b));
		  putpixel(canvas, xx + 1, yy + 1,
		           SDL_MapRGB(canvas->format, r, g, b));
			   */
		}
	    }

	  /* Then, draw dark outlines where there's a large contrast change */

	  for (yy = y - 16; yy < y + 16; yy++)
	    {
	      for (xx = x - 16; xx < x + 16; xx++)
		{
		  /* Get original color: */
		
		  SDL_GetRGB(getpixel(last, xx, yy),
		             last->format, &r, &g, &b);

		  SDL_GetRGB(getpixel(last, xx + 1, yy),
		             last->format, &r1, &g1, &b1);

		  SDL_GetRGB(getpixel(last, xx + 1, yy + 1),
		             last->format, &r2, &g2, &b2);

#define OUTLINE_THRESH 48
		  if (abs(((r + g + b) / 3) - (r1 + g1 + b1) / 3) > OUTLINE_THRESH ||
		      abs(((r + g + b) / 3) - (r2 + g2 + b2) / 3) > OUTLINE_THRESH ||
		      abs(r - r1) > OUTLINE_THRESH || abs(g - g1) > OUTLINE_THRESH || abs(b - b1) > OUTLINE_THRESH ||
		      abs(r - r2) > OUTLINE_THRESH || abs(g - g2) > OUTLINE_THRESH || abs(b - b2) > OUTLINE_THRESH)
		  {
		  	putpixel(canvas, xx - 1, yy,
		        	   SDL_MapRGB(canvas->format, 0, 0, 0));
		  	putpixel(canvas, xx, yy - 1,
		        	   SDL_MapRGB(canvas->format, 0, 0, 0));
		  	putpixel(canvas, xx - 1, yy - 1,
		        	   SDL_MapRGB(canvas->format, 0, 0, 0));
		  }
		}
	    }

	  SDL_UnlockSurface(canvas);
	  SDL_UnlockSurface(last);
	}
      else if (cur_magic == MAGIC_RAINBOW)
	{
	  /* Pick next color: */
	
	  colr = SDL_MapRGB(canvas->format,
			    rainbow_hexes[rainbow_color][0],
			    rainbow_hexes[rainbow_color][1],
			    rainbow_hexes[rainbow_color][2]);
	

	  /* Draw the shape: */

	  for (yy = 0; yy <= 16; yy++)
	    {
	      w = (yy * yy) / 16;


	      /* Top half: */
	
	      dest.x = x - 16 + w;
	      dest.w = 32 - (w * 2);
	      dest.y = y - yy;;
	      dest.h = 1;

	      SDL_FillRect(canvas, &dest, colr);
	

	      /* Bottom half: */

	      dest.x = x - 16 + w;
	      dest.w = 32 - (w * 2);
	      dest.y = y + yy;
	      dest.h = 1;

	      SDL_FillRect(canvas, &dest, colr);
	    }
	}
      else if (cur_magic == MAGIC_CHALK)
	{
	  SDL_LockSurface(last);

	  for (yy = y - 8; yy <= y + 8; yy = yy + 4)
	    {
	      for (xx = x - 8; xx <= x + 8; xx = xx + 4)
		{
		  dest.x = xx + ((rand() % 5) - 2);
		  dest.y = yy + ((rand() % 5) - 2);
		  dest.w = (rand() % 4) + 2;
		  dest.h = (rand() % 4) + 2;
	
		  colr = getpixel_last(last, clamp(0, xx, canvas->w-1),
				  clamp(0, yy, canvas->h-1));
		  SDL_FillRect(canvas, &dest, colr);
		}
	    }

	  SDL_UnlockSurface(last);
	}
      else if (cur_magic == MAGIC_DRIP)
	{
	  for (xx = x - 8; xx <= x + 8; xx++)
	    {
	      h = (rand() % 8) + 8;

	      for (yy = y; yy <= y + h; yy++)
		{
		  src.x = xx;
		  src.y = y;
		  src.w = 1;
		  src.h = 16;

		  dest.x = xx;
		  dest.y = yy;
	
		  SDL_BlitSurface(last, &src, canvas, &dest);
		}
	    }
	}
      else if (cur_magic == MAGIC_SPARKLES)
	{
	  if ((rand() % 10) < 2)
	    {
	      src.x = 0;
	      src.y = (rand() % 4) * 32;
	      src.w = 32;
	      src.h = 32;

	      dest.x = x - 16;
	      dest.y = y - 16;

	      SDL_BlitSurface(img_sparkles, &src, canvas, &dest);
	    }
	}
      else if (cur_magic == MAGIC_GRASS)
	{
	  // grass color: 82,180,17
	  static int bucket;
	  double tmp_red, tmp_green, tmp_blue;
	  Uint32 (*getpixel_grass)(SDL_Surface *, int, int);
	  
	  if (!button_down)
	    bucket = 0;
	  bucket += (3.5+(rand()/(double)RAND_MAX)) * 7.0;
	  while (bucket >= 0)
	    {
	      int rank = log2int( ((double)y/canvas->h) * (0.99+(rand()/(double)RAND_MAX)) * 64 );
	      int ah = 1<<rank;
	      bucket -= ah;
	      src.x = (rand() % 4) * 64;
	      src.y = ah;
	      src.w = 64;
	      src.h = ah;

	      dest.x = x - 32;
	      dest.y = y - 30 + (int)((rand()/(double)RAND_MAX)*30);

#if 1
              // grass color: 82,180,17

              tmp_red   = sRGB_to_linear_table[color_hexes[cur_color][0]]*2.0 + (rand()/(double)RAND_MAX);
              tmp_green = sRGB_to_linear_table[color_hexes[cur_color][1]]*2.0 + (rand()/(double)RAND_MAX);
              tmp_blue  = sRGB_to_linear_table[color_hexes[cur_color][2]]*2.0 + sRGB_to_linear_table[17];

	      getpixel_grass = getpixels[img_grass->format->BytesPerPixel];
	      
	      for (yy = 0; yy < ah; yy++)
	        {
	          for (xx = 0; xx < 64; xx++)
		    {
		      double rd, gd, bd;
		      
		      SDL_GetRGBA(getpixel_grass(img_grass, xx+src.x, yy+src.y), img_grass->format,
			         &r, &g, &b, &a);
		      
                      rd = sRGB_to_linear_table[r]*8.0 + tmp_red;
                      rd = rd * (a/255.0) / 11.0;
                      gd = sRGB_to_linear_table[g]*8.0 + tmp_green;
                      gd = gd * (a/255.0) / 11.0;
                      bd = sRGB_to_linear_table[b]*8.0 + tmp_blue;
                      bd = bd * (a/255.0) / 11.0;

		      SDL_GetRGB(getpixel_canvas(canvas, xx+dest.x, yy+dest.y), canvas->format,
			         &r, &g, &b);
	
		      r = linear_to_sRGB(sRGB_to_linear_table[r]*(1.0-a/255.0) + rd);
		      g = linear_to_sRGB(sRGB_to_linear_table[g]*(1.0-a/255.0) + gd);
		      b = linear_to_sRGB(sRGB_to_linear_table[b]*(1.0-a/255.0) + bd);

		      putpixel(canvas, xx+dest.x, yy+dest.y, SDL_MapRGB(canvas->format, r, g, b));
		    }
	        }
#else
              // untinted
	      SDL_BlitSurface(img_grass, &src, canvas, &dest);
#endif
	    }
	}
      else if (cur_magic == MAGIC_FLIP)
	{
	  /* Flip the canvas: */

	  for (yy = 0; yy < canvas->h; yy++)
	    {
	      src.x = 0;
	      src.y = yy;
	      src.w = canvas->w;
	      src.h = 1;

	      dest.x = 0;
	      dest.y = canvas->h - yy - 1;
	
	      SDL_BlitSurface(last, &src, canvas, &dest);
	    }
	  
	  
	  /* Flip starter, too! */

	  starter_flipped = !starter_flipped;

	  if (img_starter != NULL)
	    flip_starter();
	}
      else if (cur_magic == MAGIC_MIRROR)
	{
	  /* Mirror-image the canvas: */

	  for (xx = 0; xx < canvas->w; xx++)
	    {
	      src.x = xx;
	      src.y = 0;
	      src.w = 1;
	      src.h = canvas->h;

	      dest.x = canvas->w - xx - 1;
	      dest.y = 0;
	
	      SDL_BlitSurface(last, &src, canvas, &dest);
	    }


	  /* Mirror starter, too! */

	  starter_mirrored = !starter_mirrored;

	  if (img_starter != NULL)
	    mirror_starter();
	}
#if 0
      else if (cur_magic == MAGIC_THIN || cur_magic == MAGIC_THICK)
	{
	  SDL_LockSurface(last);
	  SDL_LockSurface(canvas);

	  for (xx = -8; xx <= 8; xx++)
	    {
	      for (yy = -8; yy <= 8; yy++)
		{
		  SDL_GetRGB(getpixel(last, x + xx, y + yy), last->format,
			     &r, &g, &b);

		  r = min(r, (Uint8)255);
		  g = min(g, (Uint8)255);
		  b = min(b, (Uint8)255);
	
		  if ((cur_magic == MAGIC_THIN && (((r + g + b) / 3) > 128)) ||
		      (cur_magic == MAGIC_THICK && (((r + g + b) / 3) <= 128)))
		    {
		      putpixel(canvas, x + xx + 0, y + yy - 1,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx - 1, y + yy + 0,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx + 1, y + yy + 0,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx + 0, y + yy + 1,
			       SDL_MapRGB(canvas->format, r, g, b));
	      
		      putpixel(canvas, x + xx - 1, y + yy - 1,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx - 1, y + yy + 1,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx + 1, y + yy - 1,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx + 1, y + yy + 1,
			       SDL_MapRGB(canvas->format, r, g, b));
	      
		      putpixel(canvas, x + xx + 0, y + yy - 2,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx - 2, y + yy + 0,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx + 2, y + yy + 0,
			       SDL_MapRGB(canvas->format, r, g, b));
		      putpixel(canvas, x + xx + 0, y + yy + 2,
			       SDL_MapRGB(canvas->format, r, g, b));
		    }
		}
	    }
	
	  SDL_UnlockSurface(canvas);
	  SDL_UnlockSurface(last);
	}
#endif
    }
}


/* Store canvas into undo buffer: */

static void rec_undo_buffer(void)
{
  int wanna_update_toolbar;

  wanna_update_toolbar = 0;


  SDL_BlitSurface(canvas, NULL, undo_bufs[cur_undo], NULL);
  undo_starters[cur_undo] = UNDO_STARTER_NONE;

  cur_undo = (cur_undo + 1) % NUM_UNDO_BUFS;

  if (cur_undo == oldest_undo)
    oldest_undo = (oldest_undo + 1) % NUM_UNDO_BUFS;

  newest_undo = cur_undo;

#ifdef DEBUG
  printf("DRAW: Current=%d  Oldest=%d  Newest=%d\n",
	 cur_undo, oldest_undo, newest_undo);
#endif


  /* Update toolbar buttons, if needed: */

  if (tool_avail[TOOL_UNDO] == 0)
    {
      tool_avail[TOOL_UNDO] = 1;
      wanna_update_toolbar = 1;
    }

  if (tool_avail[TOOL_REDO])
    {
      tool_avail[TOOL_REDO] = 0;
      wanna_update_toolbar = 1;
    }

  if (wanna_update_toolbar)
    {
      draw_toolbar();
      update_screen_rect(&r_tools);
    }
}


/* Show program version: */
static void show_version(void)
{
  printf("\nTux Paint\n");
  printf("  Version " VER_VERSION " (" VER_DATE ")\n");

#ifdef LOW_QUALITY_THUMBNAILS
  printf("  Low Quality Thumbnails enabled\n");
#endif

#ifdef LOW_QUALITY_COLOR_SELECTOR
  printf("  Low Quality Color Selector enabled\n");
#endif

#ifdef LOW_QUALITY_STAMP_OUTLINE
  printf("  Low Quality Stamp Outline enabled\n");
#endif

#ifdef LOW_QUALITY_FLOOD_FILL
  printf("  Low Quality Flood Fill enabled\n");
#endif

#ifdef NO_PROMPT_SHADOWS
  printf("  Prompt Shadows disabled\n");
#endif

#ifdef NOSOUND
  printf("  Sound disabled\n");
#endif

#ifdef DEBUG
  printf("  Verbose debugging enabled\n");
#endif
  
  printf("\n");
}


/* Show usage display: */

static void show_usage(FILE * f, char * prg)
{
  char * blank;
  unsigned i;

  blank = strdup(prg);

  for (i = 0; i < strlen(blank); i++)
    blank[i] = ' ';

  fprintf(f,
	  "\n"
	  "Usage: %s {--usage | --help | --version | --copying}\n"
	  "\n"
	  "  %s [--windowed | --fullscreen]\n"
	  "  %s [--640x480   | --800x600   | --1024x768 |\n"
	  "  %s  --1280x1024 | --1400x1050 | --1600x1200]\n"
	  "  %s [--sound | --nosound]         [--quit | --noquit]\n"
	  "  %s [--print | --noprint]         [--complexshapes | --simpleshapes]\n"
	  "  %s [--mixedcase | --uppercase]   [--fancycursors | --nofancycursors]\n"
	  "  %s [--mouse | --keyboard]        [--dontgrab | --grab]\n"
	  "  %s [--noshortcuts | --shortcuts] [--wheelmouse | --nowheelmouse]\n"
	  "  %s [--outlines | --nooutlines]   [--stamps | --nostamps]\n"
	  "  %s [--sysfonts | --nosysfonts]\n"
	  "  %s [--nostampcontrols | --stampcontrols]\n"
	  "  %s [--mirrorstamps | --dontmirrorstamps]\n"
	  "  %s [--saveoverask | --saveover | --saveovernew]\n"
	  "  %s [--nosave | --save]\n"
	  "  %s [--savedir DIRECTORY]\n"
#ifdef WIN32
	  "  %s [--printcfg | --noprintcfg]\n"
#endif
	  "  %s [--printdelay=SECONDS]\n"
	  "  %s [--altprintmod | --altprintalways | --altprintnever]\n"
	  "  %s [--lang LANGUAGE | --locale LOCALE | --lang help]\n"
	  "  %s [--nosysconfig] [--nolockfile]\n"
	  /* "  %s [--record FILE | --playback FILE]\n" */
	  "\n",
	  prg, prg,
	  blank, blank, blank,
	  blank, blank, blank,
	  blank, blank, blank,
	  blank, blank, blank,
	  blank, blank,
#ifdef WIN32
	  blank,
#endif
	  blank, blank, blank,
	  blank);

  free(blank);
}


// The original Tux Paint canvas was 448x376. The canvas can be
// other sizes now, but many old stamps are sized for the small
// canvas. So, with larger canvases, we must choose a good scale
// factor to compensate. As the canvas size grows, the user will
// want a balance of "more stamps on the screen" and "stamps not
// getting tiny". This will calculate the needed scale factor.
static unsigned compute_default_scale_factor(double ratio)
{
  double old_diag = sqrt(448*448+376*376);
  double new_diag = sqrt(canvas->w*canvas->w+canvas->h*canvas->h);
  double good_def = ratio*sqrt(new_diag/old_diag);
  double good_log = log(good_def);
  unsigned defsize = HARD_MAX_STAMP_SIZE;
  while(defsize>0)
    {
      double this_err = good_log - log(scaletable[defsize].numer / (double)scaletable[defsize].denom);
      double next_err = good_log - log(scaletable[defsize-1].numer / (double)scaletable[defsize-1].denom);
      if( fabs(next_err) > fabs(this_err) ) break;
      defsize--;
    }
  return defsize;
}


// backdoor into qsort operations, so we don't have to do work again
static int was_bad_font;

// see if two font surfaces are the same
static int do_surfcmp(const SDL_Surface *const *const v1, const SDL_Surface *const *const v2)
{
  const SDL_Surface *const s1 = *v1;
  const SDL_Surface *const s2 = *v2;
  int width;
  int cmp;
  int i;

  if(s1==s2)
    {
      printf("s1==s2?\n");
      return 0;
    }
  if(!s1 || !s2 || !s1->w || !s2->w || !s1->h || !s2->h || !s1->format || !s2->format)
    {
      was_bad_font = 1;
      return 0;
    }
  if(s1->format->BytesPerPixel != s2->format->BytesPerPixel)
    {
      // something really strange and bad happened
      was_bad_font = 1;
      return s1->format->BytesPerPixel - s2->format->BytesPerPixel;
    }

  if(s1->w != s2->w)
    return s1->w - s2->w;
  if(s1->h != s2->h)
    return s1->h - s2->h;

  {
    const char *const c1 = (char *const)s1->pixels;
    const char *const c2 = (char *const)s2->pixels;
    width = s1->format->BytesPerPixel * s1->w;
    if(width==s1->pitch)
      return memcmp(c1,c2,width*s1->h);
    cmp = 0;
    i = s1->h;
    while(i--)
      {
	cmp = memcmp(c1 + i*s1->pitch, c2 + i*s2->pitch, width);
	if(cmp)
	  break;
      }
  }

  return cmp;
}

// see if two font surfaces are the same
static int surfcmp(const void *s1, const void *s2)
{
  int diff = do_surfcmp(s1, s2);
  if(!diff)
    was_bad_font = 1;
  return diff;
}

// check if the characters will render distinctly
static int charset_works(TTF_Font *font, const char *s)
{
  SDL_Color black = {0, 0, 0, 0};
  SDL_Surface **surfs = malloc(strlen(s) * sizeof surfs[0]);
  unsigned count = 0;
  int ret = 0;
  while(*s)
    {
      char c[8];
      unsigned offset = 0;
      SDL_Surface *tmp_surf;
      do
        c[offset++] = *s++;
        while((*s & 0xc0u) == 0x80u); // assume safe input
      c[offset++] = '\0';
      tmp_surf = TTF_RenderUTF8_Blended(font, c, black);
      if(!tmp_surf)
        {
#if 0
// THREADED_FONTS
          printf("could not render \"%s\" font\n", TTF_FontFaceFamilyName(font));
#endif
          goto out;
        }
      surfs[count++] = tmp_surf;
    }
  was_bad_font = 0;
  qsort(surfs, count, sizeof surfs[0], surfcmp);
  ret = !was_bad_font;
out:
  while(count--)
  {
    if (surfs[count] == NULL)
      printf("TRYING TO RE-FREE!");
    else
    {
      SDL_FreeSurface(surfs[count]);
      surfs[count] = NULL;
    }
  }
  free(surfs);
  return ret;
}


/////////////////////////////// directory tree walking /////////////////////

#define TP_FTW_UNKNOWN 1
#define TP_FTW_DIRECTORY 2
#define TP_FTW_NORMAL 0

#define TP_FTW_PATHSIZE 400

typedef struct tp_ftw_str {
  char *str;
  unsigned char len;
//  unsigned char is_rsrc;
} tp_ftw_str;

// For qsort()
static int compare_ftw_str(const void *v1, const void *v2)
{
  const char *restrict const s1 = ((tp_ftw_str*)v1)->str;
  const char *restrict const s2 = ((tp_ftw_str*)v2)->str;
  return -strcmp(s1, s2);
}

static void tp_ftw(char *restrict const dir, unsigned dirlen, int rsrc,
  void (*fn)(const char *restrict const dir, unsigned dirlen, tp_ftw_str *files, unsigned count)
  )
{
  DIR *d;
  unsigned num_file_names = 0;
  unsigned max_file_names = 0;
  tp_ftw_str *file_names = NULL;
  unsigned num_dir_names = 0;
  unsigned max_dir_names = 0;
  tp_ftw_str *dir_names = NULL;
  int d_namlen;
  int add_rsrc;

  dir[dirlen++] = '/';
  dir[dirlen] = '\0';
//printf("processing directory %s %d\n", dir, dirlen);
  /* Open the directory: */
  d = opendir(dir);
  if (!d)
    return;

  for(;;)
    {
      struct dirent * f = readdir(d);
      int filetype = TP_FTW_UNKNOWN;

      if(!f)
        break;
      if(f->d_name[0]=='.')
        continue;
// Linux and BSD can often provide file type info w/o the stat() call
#ifdef DT_UNKNOWN
      switch(f->d_type)
        {
          default:
            continue;
          case DT_REG:
            if(!rsrc)  // if maybe opening resource files, need st_size
              filetype = TP_FTW_NORMAL;
            break;
          case DT_DIR:
            filetype = TP_FTW_DIRECTORY;
            break;
          case DT_UNKNOWN:
          case DT_LNK:
            ;
        }
#else
#warning Failed to see DT_UNKNOWN
#endif

#if defined(_DIRENT_HAVE_D_NAMLEN) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
      d_namlen = f->d_namlen;
#else
      d_namlen = strlen(f->d_name);
#endif
      add_rsrc = 0;

      if(filetype == TP_FTW_UNKNOWN)
        {
          struct stat sbuf;
          memcpy(dir+dirlen, f->d_name, d_namlen+1);
          if(stat(dir, &sbuf))
            continue;  // oh well... try the next one
          if(S_ISDIR(sbuf.st_mode))
            filetype = TP_FTW_DIRECTORY;
          else if(S_ISREG(sbuf.st_mode))
            {
              filetype = TP_FTW_NORMAL;
              if(rsrc && !sbuf.st_size)
                add_rsrc = 5;  // 5 is length of "/rsrc"
            }
          else
            continue;  // was a device file or somesuch
        }
      if(filetype==TP_FTW_NORMAL)
        {
          char *cp;

          if(num_file_names==max_file_names)
            {
              max_file_names = max_file_names * 5 / 4 + 30;
              file_names = realloc(file_names, max_file_names * sizeof *file_names);
            }
          cp = malloc(d_namlen + add_rsrc + 1);
          memcpy(cp, f->d_name, d_namlen);
          if(add_rsrc)
            memcpy(cp+d_namlen, "/rsrc", 6);
          else
            cp[d_namlen] = '\0';
          file_names[num_file_names].str = cp;
          file_names[num_file_names].len = d_namlen;
          num_file_names++;
        }
      if(filetype==TP_FTW_DIRECTORY)
        {
	  char * cp;
          if(num_dir_names==max_dir_names)
            {
              max_dir_names = max_dir_names * 5 / 4 + 3;
              dir_names = realloc(dir_names, max_dir_names * sizeof *dir_names);
            }
          cp = malloc(d_namlen + 1);
          memcpy(cp, f->d_name, d_namlen + 1);
          dir_names[num_dir_names].str = cp;
          dir_names[num_dir_names].len = d_namlen;
          num_dir_names++;
        }
    }

  closedir(d);
  show_progress_bar();
  dir[dirlen] = '\0';   // repair it (clobbered for stat() call above)

  if(file_names)
    {
// let callee sort and keep the string
#if 0
      qsort(file_names, num_file_names, sizeof *file_names, compare_ftw_str);
      while(num_file_names--)
        {
          free(file_names[num_file_names].str);
        }
      free(file_names);
#else
      fn(dir, dirlen, file_names, num_file_names);
#endif
    }

  if(dir_names)
    {
      qsort(dir_names, num_dir_names, sizeof *dir_names, compare_ftw_str);
      while(num_dir_names--)
        {
          memcpy(dir+dirlen, dir_names[num_dir_names].str, dir_names[num_dir_names].len+1);
          tp_ftw(dir, dirlen+dir_names[num_dir_names].len, rsrc, fn);
          free(dir_names[num_dir_names].str);
        }
      free(dir_names);
    }
}


///////////////// directory walking callers and callbacks //////////////////

static void loadfont_callback(const char *restrict const dir, unsigned dirlen, tp_ftw_str *files, unsigned i)
{
  dirlen = dirlen;

  while(i--)
    {
      int loadable = 0;
      const char *restrict const cp = strchr(files[i].str, '.');
      show_progress_bar();
      if(cp)
        {
          // need gcc 3.4 for the restrict in this location
          const char * /*restrict*/ const suffixes[] = {"ttc", "dfont", "pfa", "pfb", "otf", "ttf",};
          int j = sizeof suffixes / sizeof suffixes[0];
          while(j--)
            {
              // only check part, because of potential .gz or .bz2 suffix
              if(!strncasecmp(cp+1,suffixes[j],strlen(suffixes[j])))
                {
                  loadable = 1;
                  break;
                }
            }
        }
      if(!loadable)
        {
          if(strcasestr(files[i].str, "/rsrc"))
            loadable = 1;
        }
      // Loadable: TrueType (.ttf), OpenType (.otf), Type1 (.pfa and .pfb),
      // and various useless bitmap fonts. Compressed files (with .gz or .bz2)
      // should also work. A *.dfont is pretty much a Mac resource fork in a normal
      // file, and may load with some library versions.
      if (loadable)
        {
          char fname[512];
          TTF_Font *font;
          snprintf(fname, sizeof fname, "%s/%s", dir, files[i].str);
//printf("Loading font: %s\n", fname);
          font = TTF_OpenFont(fname, text_sizes[text_size]);
          if(font)
            {
              const char *restrict const family = TTF_FontFaceFamilyName(font);
              const char *restrict const style = TTF_FontFaceStyleName(font);


#ifdef DEBUG
              int numfaces = TTF_FontFaces(font);
              if (numfaces != 1)
                printf("Found %d faces in %s, %s, %s\n", numfaces, files[i].str, family, style);
#endif

              if(strcmp("Zapfino",family) && strcmp("Elvish Ring NFI",family) && charset_works(font, gettext("jq")) && charset_works(font, gettext("JQ")))
                {
                  if (num_font_styles==num_font_styles_max)
                    {
                      num_font_styles_max = num_font_styles_max * 5 / 4 + 30;
                      user_font_styles = realloc(user_font_styles, num_font_styles_max * sizeof *user_font_styles);
                    }
                  user_font_styles[num_font_styles] = malloc(sizeof *user_font_styles[num_font_styles]);
                  user_font_styles[num_font_styles]->directory = strdup(dir);
                  user_font_styles[num_font_styles]->filename = files[i].str; // steal it (mark NULL below)
                  user_font_styles[num_font_styles]->family = strdup(family);
                  user_font_styles[num_font_styles]->style = strdup(style);
                  user_font_styles[num_font_styles]->score  = charset_works(font, gettext("oO"));
                  user_font_styles[num_font_styles]->score += charset_works(font, gettext("`\%_@$~#{}<>^&*"));
                  user_font_styles[num_font_styles]->score += charset_works(font, gettext(",.?!"));
                  user_font_styles[num_font_styles]->score += charset_works(font, gettext("017"));
                  user_font_styles[num_font_styles]->score += charset_works(font, gettext("O0"));
                  user_font_styles[num_font_styles]->score += charset_works(font, gettext("1Il|"));
                  num_font_styles++;
//printf("Accepted: %s, %s, %s\n", files[i].str, family, style);
                  files[i].str = NULL;  // so free() won't crash -- we stole the memory
                }
              else
                {
#if 0
// THREADED_FONTS
                  printf("Font is too defective: %s, %s, %s\n", files[i].str, family, style);
#endif
                }
              TTF_CloseFont(font);
            }
          else
            {
#if 0
// THREADED_FONTS
              printf("could not open %s\n", files[i].str);
#endif
            }
        }
      free(files[i].str);
    }
  free(files);
}



static void loadfonts(const char * const dir)
{
  char buf[TP_FTW_PATHSIZE];
  unsigned dirlen = strlen(dir);
  
  memcpy(buf,dir,dirlen);
  tp_ftw(buf, dirlen, 1, loadfont_callback);
}  


static void loadbrush_callback(const char *restrict const dir, unsigned dirlen, tp_ftw_str *files, unsigned i)
{
  dirlen = dirlen;

  qsort(files, i, sizeof *files, compare_ftw_str);
  while(i--)
    {
      show_progress_bar();
      if (strcasestr(files[i].str, ".png"))
        {
          char fname[512];
          snprintf(fname, sizeof fname, "%s/%s", dir, files[i].str);
          if (num_brushes==num_brushes_max)
            {
              num_brushes_max = num_brushes_max * 5 / 4 + 4;
              img_brushes = realloc(img_brushes, num_brushes_max * sizeof *img_brushes);
            }
          img_brushes[num_brushes] = loadimage(fname);
          num_brushes++;
        }
      free(files[i].str);
    }
  free(files);
}



static void load_brush_dir(const char * const dir)
{
  char buf[TP_FTW_PATHSIZE];
  unsigned dirlen = strlen(dir);
  memcpy(buf,dir,dirlen);
  tp_ftw(buf, dirlen, 0, loadbrush_callback);
}

SDL_Surface * mirror_surface(SDL_Surface *s)
{
  SDL_Surface * new_surf;
  int x;
  SDL_Rect src, dest;

  
  /* Mirror surface: */

  new_surf = duplicate_surface(s);
  
  if (new_surf != NULL)
  {
    for (x = 0; x < s->w; x++)
    {
      src.x = x;
      src.y = 0;
      src.w = 1;
      src.h = s->h;

      dest.x = s->w - x - 1;
      dest.y = 0;

      SDL_BlitSurface(s, &src, new_surf, &dest);
    }
    
    SDL_FreeSurface(s);
    
    return(new_surf);
  }
  else
  {
    return(s);
  }
}

SDL_Surface * flip_surface(SDL_Surface *s)
{
  SDL_Surface * new_surf;
  int y;
  SDL_Rect src, dest;

  
  /* Flip surface: */

  new_surf = duplicate_surface(s);

  if (new_surf != NULL)
  {
    for (y = 0; y < s->h; y++)
    {
      src.x = 0;
      src.y = y;
      src.w = s->w;
      src.h = 1;

      dest.x = 0;
      dest.y = s->h - y - 1;

      SDL_BlitSurface(s, &src, new_surf, &dest);
    }
    
    SDL_FreeSurface(s);
    
    return(new_surf);
  }
  else
  {
    return(s);
  }
}

static unsigned default_stamp_size;

static void loadstamp_finisher(stamp_type *sd, unsigned w, unsigned h, double ratio)
{
  unsigned int upper = HARD_MAX_STAMP_SIZE;
  unsigned int lower = 0;
  unsigned mid;

  /* If Tux Paint is in mirror-image-by-default mode, mirror, if we can: */
  if (mirrorstamps && sd->mirrorable)
    sd->mirrored = 1;

  do
  {
    scaleparams *s = &scaletable[upper];
    int pw, ph; // proposed width and height

    pw = (w * s->numer + s->denom - 1) / s->denom;
    ph = (h * s->numer + s->denom - 1) / s->denom;

    // OK to let a stamp stick off the sides in one direction, not two
    if (pw < canvas->w * 2 && ph < canvas->h * 1)
      break;
    if (pw < canvas->w * 1 && ph < canvas->h * 2)
      break;
  }
  while (--upper);

  do
  {
    scaleparams *s = &scaletable[lower];
    int pw, ph; // proposed width and height

    pw = (w * s->numer + s->denom - 1) / s->denom;
    ph = (h * s->numer + s->denom - 1) / s->denom;

    if (pw*ph > 20)
      break;
  }
  while (++lower < HARD_MAX_STAMP_SIZE);

  if(upper<lower)
  {
    // this, if it ever happens, is very bad
    upper = (upper+lower)/2;
    lower = upper;
  }

  mid = default_stamp_size;
  if(ratio != 1.0)
     mid = compute_default_scale_factor(ratio);

  if(mid > upper)
    mid = upper;
  
  if(mid < lower)
    mid = lower;

  sd->min  = lower;
  sd->size = mid;
  sd->max  = upper;
}


// Note: must have read the *.dat file before calling this
static void set_active_stamp(void)
{
  stamp_type *sd = stamp_data[cur_stamp];
  unsigned len = strlen(sd->stampname);
  char *buf = alloca(len + strlen("_mirror.png") + 1);

  if(active_stamp)
    SDL_FreeSurface(active_stamp);
  active_stamp = NULL;
  
  memcpy(buf, sd->stampname, len);

  if(sd->mirrored && !sd->no_premirror)
    {
      memcpy(buf+len, "_mirror.png", 12);
      active_stamp = do_loadimage(buf, 0);
    }
  if(!active_stamp)
    {
      memcpy(buf+len, ".png", 5);
      active_stamp = do_loadimage(buf, 0);
      if(!active_stamp)
        active_stamp = thumbnail(img_dead40x40, 40, 40, 1);  // copy it
      if(sd->mirrored)
        active_stamp = mirror_surface(active_stamp);
    }

  if(sd->flipped)
    active_stamp = flip_surface(active_stamp);
}

static void get_stamp_thumb(stamp_type *sd)
{
  SDL_Surface *bigimg = NULL;
  unsigned len = strlen(sd->stampname);
  char *buf = alloca(len + strlen("_mirror.png") + 1);
  SDL_Surface *wrongmirror = NULL;
  int need_mirror = 0;
  double ratio;
  unsigned w;
  unsigned h;

  memcpy(buf, sd->stampname, len);

  if(!sd->processed)
    {
      memcpy(buf+len, ".dat", 5);
      ratio = loadinfo(buf, sd);
    }
  else
    ratio = 1.0;

#ifndef NOSOUND
  // good time to load the sound
  if(!sd->no_sound && !sd->ssnd && use_sound)
    {
      // damn thing wants a .png extension; give it one
      memcpy(buf+len, ".png", 5);
      sd->ssnd = loadsound(buf);
      sd->no_sound = !sd->ssnd;
    }
#endif

  if(!sd->no_txt && !sd->stxt)
    {
      // damn thing wants a .png extension; give it one
      memcpy(buf+len, ".png", 5);
      sd->stxt = loaddesc(buf);
      sd->no_txt = !sd->stxt;
    }

  // first see if we can re-use an existing thumbnail
  if(sd->thumbnail)
    {
      if(sd->mirrored == sd->thumb_mirrored)
        {
          if(sd->flipped  == sd->thumb_flipped)
            return;
          sd->thumbnail = flip_surface(sd->thumbnail);
          sd->thumb_flipped = !sd->thumb_flipped;
          return;
        }
      wrongmirror = sd->thumbnail;
    }

  // nope, unless perhaps it can be mirrored
  if(sd->mirrored && !sd->no_premirror)
    {
      memcpy(buf+len, "_mirror.png", 12);
      bigimg = do_loadimage(buf, 0);
      if(bigimg)
        {
          if(wrongmirror)
            SDL_FreeSurface(wrongmirror);
        }
      else
        sd->no_premirror = 1;
    }
  if(wrongmirror && sd->no_premirror)
    {
      wrongmirror = mirror_surface(wrongmirror);
      sd->thumbnail = wrongmirror;
      sd->thumb_mirrored = !sd->thumb_mirrored;

      if(sd->flipped  == sd->thumb_flipped)
        return;
      sd->thumbnail = flip_surface(sd->thumbnail);
      sd->thumb_flipped = !sd->thumb_flipped;
      return;
    }
  if(!bigimg)
    {
      memcpy(buf+len, ".png", 5);
      bigimg = do_loadimage(buf, 0);
      if(sd->mirrored)
        need_mirror = 1;  // want to mirror after scaling
    }

  w = 40;
  h = 40;
  if(bigimg)
    {
      w = bigimg->w;
      h = bigimg->h;
    }

  if(!bigimg)
    sd->thumbnail = thumbnail(img_dead40x40, 40, 40, 1); // copy
  else if (bigimg->w > 40 || bigimg->h > 40)
    {
      sd->thumbnail = thumbnail(bigimg, 40, 40, 1);
      SDL_FreeSurface(bigimg);
    }
  else
    sd->thumbnail = bigimg;

  if(need_mirror)
    sd->thumbnail = mirror_surface(sd->thumbnail);
  sd->thumb_mirrored = sd->mirrored;

  if(sd->flipped)
    sd->thumbnail = flip_surface(sd->thumbnail);
  sd->thumb_flipped = sd->flipped;

  if(sd->processed)
    return;
  sd->processed = 1; // not really, but on the next line...
  loadstamp_finisher(sd, w, h, ratio);
}


static void loadstamp_callback(const char *restrict const dir, unsigned dirlen, tp_ftw_str *files, unsigned i)
{
  qsort(files, i, sizeof *files, compare_ftw_str);
  while(i--)
    {
      char fname[512];
      char *dotpng = strcasestr(files[i].str, ".png");

      show_progress_bar();

      if (dotpng>files[i].str && !strcasecmp(dotpng,".png") && (dotpng-files[i].str+1+dirlen < sizeof fname) && !strcasestr(files[i].str, "_mirror.png"))
        {
          snprintf(fname, sizeof fname, "%s/%s", dir, files[i].str);
          if(num_stamps == max_stamps)
            {
              max_stamps = max_stamps * 5 / 4 + 15;
              stamp_data = realloc(stamp_data, max_stamps * sizeof *stamp_data);
            }
          stamp_data[num_stamps] = calloc(1, sizeof *stamp_data[num_stamps]);
          stamp_data[num_stamps]->stampname = malloc(dotpng-files[i].str+1+dirlen+1);
          memcpy(stamp_data[num_stamps]->stampname, fname, dotpng-files[i].str+1+dirlen);
          stamp_data[num_stamps]->stampname[dotpng-files[i].str+1+dirlen] = '\0';
          num_stamps++;
        }
      free(files[i].str);
    }
  free(files);
}



static void load_stamp_dir(const char * const dir)
{
  char buf[TP_FTW_PATHSIZE];
  unsigned dirlen = strlen(dir);
  memcpy(buf,dir,dirlen);
  tp_ftw(buf, dirlen, 0, loadstamp_callback);
}


static void load_stamps(void)
{
  char * homedirdir = get_fname("stamps");

  default_stamp_size = compute_default_scale_factor(1.0);

  load_stamp_dir(homedirdir);
  load_stamp_dir(DATA_PREFIX "stamps");
#ifdef __APPLE__
  load_stamp_dir("/Library/Application Support/TuxPaint/stamps");
#endif
#ifdef WIN32
  free(homedirdir);
  homedirdir = get_fname("data/stamps");
  load_stamp_dir(homedirdir);
#endif

  if (num_stamps == 0)
    {
      fprintf(stderr,
              "\nWarning: No stamps found in " DATA_PREFIX "stamps/\n"
              "or %s\n\n", homedirdir);
    }

  free(homedirdir);
}



static int load_user_fonts(void *vp)
{
  char *homedirdir;

  (void)vp; // junk passed by threading library

  loadfonts(DATA_PREFIX "fonts");

  if (!no_system_fonts)
  {
#ifdef WIN32
    loadfonts("%SystemRoot%\\Fonts");
#elif defined(__BEOS__)
    loadfonts("/boot/home/config/font/ttffonts");
    loadfonts("/usr/share/fonts");
    loadfonts("/usr/X11R6/lib/X11/fonts");
#elif defined(__APPLE__)
    loadfonts("/System/Library/Fonts");
    loadfonts("/Library/Fonts");
    loadfonts(macosx.fontsPath);
    loadfonts("/usr/share/fonts");
    loadfonts("/usr/X11R6/lib/X11/fonts");
#elif defined(__sun__)
    loadfonts("/usr/openwin/lib/X11/fonts");
    loadfonts("/usr/share/fonts");
    loadfonts("/usr/X11R6/lib/X11/fonts");
#else
    loadfonts("/usr/share/feh/fonts");
    loadfonts("/usr/share/fonts");
    loadfonts("/usr/X11R6/lib/X11/fonts");
    loadfonts("/usr/share/texmf/fonts");
    loadfonts("/usr/share/grace/fonts/type1");
    loadfonts("/usr/share/hatman/fonts");
    loadfonts("/usr/share/icewm/themes/jim-mac");
    loadfonts("/usr/share/vlc/skins2/fonts");
    loadfonts("/usr/share/xplanet/fonts");
#endif
  }

  homedirdir = get_fname("fonts");
  loadfonts(homedirdir);
  free(homedirdir);
#ifdef WIN32
  homedirdir = get_fname("data/fonts");
  loadfonts(homedirdir);
  free(homedirdir);
#endif

  groupfonts();

  font_thread_done = 1;
  waiting_for_fonts = 0;
  // FIXME: need a memory barrier here
  return 0; // useless, wanted by threading library
}


#ifdef FORKED_FONTS

#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/poll.h>
#include <sys/wait.h>

#ifdef _POSIX_PRIORITY_SCHEDULING
#include <sched.h>
#else
#define sched_yield()
#endif

#ifdef __linux__
#include <sys/prctl.h>
#else
#define prctl(o,a1)
#define PR_SET_PDEATHSIG 0
#endif

static void reliable_write(int fd, const void *buf, size_t count)
{
  struct pollfd p;
  do
    {
      ssize_t rc = write(fd,buf,count);
      if(rc==-1)
        {
          switch(errno)
            {
              default:
                return;
              case EAGAIN:
              case ENOSPC:
                ; // satisfy a C syntax abomination
                p = (struct pollfd){fd, POLLOUT, 0};
                poll(&p, 1, -1);  // try not to burn CPU time
                // FALL THROUGH
              case EINTR:
                continue;
            }
        }
      buf += rc;
      count -= rc;
    } while(count);
}


static void reliable_read(int fd, void *buf, size_t count)
{
  struct pollfd p;
  do
    {
      ssize_t rc = read(fd,buf,count);
      if(rc==-1)
        {
          switch(errno)
            {
              default:
                return;
              case EAGAIN:
                ; // satisfy a C syntax abomination
                p = (struct pollfd){fd, POLLIN, 0};
                poll(&p, 1, -1);  // try not to burn CPU time
                // FALL THROUGH
              case EINTR:
                continue;
            }
        }
      if(rc==0)
        break;  // EOF. Better not happen before the end!
      buf += rc;
      count -= rc;
    } while(count);
}


static void run_font_scanner(void)
{
  int sv[2];
  int size, i;
  char * buf, * walk;

  if(socketpair(AF_UNIX, SOCK_STREAM, 0, sv))
    exit(42);
  font_scanner_pid = fork();
  if(font_scanner_pid)
    {
      // parent (or error -- but we're screwed in that case)
      font_socket_fd = sv[0];
      close(sv[1]);
      return;
    }
  nice(42);       // be nice, letting the main thread get the CPU
  sched_yield();     // try to let the parent run right now
  prctl(PR_SET_PDEATHSIG, 9);    // get killed if parent exits
  font_socket_fd = sv[1];
  close(sv[0]);
  progress_bar_disabled = 1;
  reliable_read(font_socket_fd, &no_system_fonts, sizeof no_system_fonts);
  sched_yield();     // try to let the parent run right now
  SDL_Init(SDL_INIT_NOPARACHUTE);
  TTF_Init();
  load_user_fonts(NULL);

  size = 0;
  i = num_font_families;
  while(i--)
    {
      char *s;
      s = user_font_families[i]->directory;
      if(s) size += strlen(s);
      s = user_font_families[i]->family;
      if(s) size += strlen(s);
      s = user_font_families[i]->filename[0];
      if(s) size += strlen(s);
      s = user_font_families[i]->filename[1];
      if(s) size += strlen(s);
      s = user_font_families[i]->filename[2];
      if(s) size += strlen(s);
      s = user_font_families[i]->filename[3];
      if(s) size += strlen(s);
      size += 6;  // for '\0' on each of the above
    }
  size += 2;  // for 2-byte font count
  buf = malloc(size);
  walk = buf;
#ifdef DEBUG
  printf("Sending %u bytes with %u families.\n", size, num_font_families);
#endif
  *walk++ = num_font_families & 0xffu;
  *walk++ = num_font_families >> 8u;
  i = num_font_families;
  while(i--)
    {
      int len;
      char *s;

      s = user_font_families[i]->directory;
      if(s)
        {
          len = strlen(s);
          memcpy(walk, s, len);
          walk += len;
        }
      *walk++ = '\0';
      
      s = user_font_families[i]->family;
      if(s)
        {
          len = strlen(s);
          memcpy(walk, s, len);
          walk += len;
        }
      *walk++ = '\0';
      
      s = user_font_families[i]->filename[0];
      if(s)
        {
          len = strlen(s);
          memcpy(walk, s, len);
          walk += len;
        }
      *walk++ = '\0';
      
      s = user_font_families[i]->filename[1];
      if(s)
        {
          len = strlen(s);
          memcpy(walk, s, len);
          walk += len;
        }
      *walk++ = '\0';
      
      s = user_font_families[i]->filename[2];
      if(s)
        {
          len = strlen(s);
          memcpy(walk, s, len);
          walk += len;
        }
      *walk++ = '\0';
      
      s = user_font_families[i]->filename[3];
      if(s)
        {
          len = strlen(s);
          memcpy(walk, s, len);
          walk += len;
        }
      *walk++ = '\0';
    }
  reliable_write(font_socket_fd, buf, size);
  exit(0);
}

static void receive_some_font_info(void)
{
  char *buf = NULL;
  unsigned buf_size = 0;
  unsigned buf_fill = 0;
  ssize_t rc;
  struct pollfd p;
  int status;
  /* unsigned */ char *walk;
  unsigned i;
  family_info *fip;

  fcntl(font_socket_fd, F_SETFL, O_NONBLOCK);
  for(;;)
    {
      if(buf_size <= buf_fill*9/8+128)
        {
          buf_size = buf_size*5/4+256;
          buf = realloc(buf, buf_size);
        }
      rc = read(font_socket_fd, buf+buf_fill, buf_size-buf_fill);
#ifdef DEBUG
printf("read: fd=%d buf_fill=%u buf_size=%u rc=%ld\n", font_socket_fd, buf_fill, buf_size, rc);
#endif

      if(rc==-1)
        {
          switch(errno)
            {
              default:
                return;
              case EAGAIN:
                ; // satisfy a C syntax abomination
                p = (struct pollfd){font_socket_fd, POLLIN, 0};
                show_progress_bar();
                poll(&p, 1, 29);  // try not to burn CPU time
                continue;
              case EINTR:
                continue;
            }
        }
      buf_fill += rc;
      if(!rc || font_thread_aborted)
        break;
    }
  close(font_socket_fd);

  waitpid(font_scanner_pid,&status,0);
  if(WIFSIGNALED(status) || font_thread_aborted)
    {
      printf("child killed by signal %u\n", WTERMSIG(status));
      user_font_families = NULL;
      num_font_families = 0;
      font_thread_done = 1;
      
      return;
    }

  show_progress_bar();
  walk = buf;
  num_font_families = *walk++;
  num_font_families += *walk++ << 8u;
#ifdef DEBUG
  printf("Got %u bytes with %u families.\n", buf_fill, num_font_families);
#endif
  user_font_families = malloc(num_font_families * sizeof *user_font_families);
  fip = malloc(num_font_families * sizeof **user_font_families);
  i = num_font_families;
  while(i--)
    {
      unsigned len;
      user_font_families[i] = fip+i;

      len = strlen(walk);
      user_font_families[i]->directory = len ? walk : NULL;
      walk += len + 1;

      len = strlen(walk);
      user_font_families[i]->family = len ? walk : NULL;
      walk += len + 1;

      len = strlen(walk);
      user_font_families[i]->filename[0] = len ? walk : NULL;
      walk += len + 1;

      len = strlen(walk);
      user_font_families[i]->filename[1] = len ? walk : NULL;
      walk += len + 1;

      len = strlen(walk);
      user_font_families[i]->filename[2] = len ? walk : NULL;
      walk += len + 1;

      len = strlen(walk);
      user_font_families[i]->filename[3] = len ? walk : NULL;
      walk += len + 1;

      user_font_families[i]->handle = NULL;
      
      // score left uninitialized
    }
  font_thread_done = 1;
}
#endif

////////////////////////////////////////////////////////////////////////////////
/* Setup: */

static void setup(int argc, char * argv[])
{
  int i, ok_to_use_sysconfig;
  char str[128];
  char * upstr;
  SDL_Color black = {0, 0, 0, 0};
  char * homedirdir;
  FILE * fi;
  SDL_Surface * tmp_surf;
  SDL_Rect dest;
  int scale;
#ifndef LOW_QUALITY_COLOR_SELECTOR
  int x, y;
  SDL_Surface * tmp_btn_up;
  SDL_Surface * tmp_btn_down;
  Uint8 r, g, b;
#endif
  SDL_Surface * tmp_imgcurup, * tmp_imgcurdown;
  Uint32 init_flags;
  char tmp_str[128];
  SDL_Surface *img1;
  SDL_Surface *img2;
  Uint32 (*getpixel_tmp_btn_up)(SDL_Surface *, int, int);
  Uint32 (*getpixel_tmp_btn_down)(SDL_Surface *, int, int);
  Uint32 (*getpixel_img2)(SDL_Surface *, int, int);
  


#if defined(__BEOS__) || defined(WIN32)
  /* if run from gui, like OpenTracker in BeOS or Explorer in Windows,
     find path from which binary was run and change dir to it
     so all files will be local :) */
  /* UPDATE (2004.10.06): Since SDL 1.2.7 SDL sets that path correctly,
     so this code wouldn't be needed if SDL was init before anything else,
     (just basic init, window shouldn't be needed). */
  /* UPDATE (2005 July 19): Enable and make work on Windows. Makes testing
     with MINGW/MSYS easier */

  if (argc && argv[0])
    {
      char *app_path = strdup(argv[0]);
      char *slash = strrchr(app_path, '/');

      if (!slash)
      {
        slash = strrchr(app_path, '\\');
      }
      if (slash)
      {
        *(slash + 1) = '\0';
        chdir(app_path);
      }
      free(app_path);
    }
#endif


  /* Set default options: */

  use_sound = 1;
  mute = 0;
  fullscreen = 0;
  noshortcuts = 0;
  dont_do_xor = 0;
  keymouse = 0;
  wheely = 1;
  no_button_distinction = 0;
  grab_input = 0;
  no_fancy_cursors = 0;
  simple_shapes = 0;
  only_uppercase = 0;
  promptless_save = SAVE_OVER_PROMPT;
  alt_print_command_default = ALTPRINT_MOD;
  disable_quit = 0;
  disable_save = 0;
  disable_print = 0;
  dont_load_stamps = 0;
  no_system_fonts = 1;
  print_delay = 0;
  use_print_config = 0;
  mirrorstamps = 0;
  disable_stamp_controls = 0;
  //  WINDOW_WIDTH = 640;
  //  WINDOW_HEIGHT = 480;
  WINDOW_WIDTH = 800;
  WINDOW_HEIGHT = 600;
  playfile = NULL;
  recording = 0;
  playing = 0;
  ok_to_use_lockfile = 1;
  

#ifdef __BEOS__
  /* Fancy cursors on BeOS are buggy in SDL */

  no_fancy_cursors = 1;
#endif


#ifdef WIN32
  savedir = GetDefaultSaveDir("TuxPaint");
#elif __BEOS__
  savedir = strdup("./userdata");
#else
  savedir = NULL;
#endif



  /* Load options from global config file: */


  /* Check to see if it's ok first: */

  ok_to_use_sysconfig = 1;
  
  for (i = 1; i < argc; i++)
    {
      if (strcmp(argv[i], "--nosysconfig") == 0)
	{
	  ok_to_use_sysconfig = 0;
	  i = argc;  /* aka break; */
	}
    }
  

  if (ok_to_use_sysconfig)
    {
#ifndef WIN32
      snprintf(str, sizeof(str), "%s/tuxpaint.conf", CONFDIR);
#else
      /* Global config file in the application directory on Windows */
      strcpy(str, "tuxpaint.cfg");
#endif
    
      fi = fopen(str, "r");
      if (fi != NULL)
	{
	  parse_options(fi);
	  fclose(fi);
	}
      else
	debug(str);
    }



  /* Load options from user's own configuration (".rc" / ".cfg") file: */

#if defined(WIN32)
  /* Default local config file in users savedir directory on Windows */
  snprintf(str, sizeof(str), "%s/tuxpaint.cfg", savedir);
#elif defined(__BEOS__)
  /* BeOS: Use a "tuxpaint.cfg" file: */
  
  strcpy(str, "tuxpaint.cfg");

  //#elif __APPLE__
  /* Mac: ??? */
  /* FIXME! */
  
#else
  /* Linux and other Unixes:  Use 'rc' style (~/.tuxpaintrc) */
  
  if (getenv("HOME") != NULL)
    {
      /* Should it be "~/.tuxpaint/tuxpaintrc" instead???
	 Comments welcome ... bill@newbreedsoftware.com */

      snprintf(str, sizeof(str), "%s/.tuxpaintrc", getenv("HOME"));
    }
  else
    {
      /* WOAH! We don't know what our home directory is!? Last resort,
	 do it Windows/BeOS way: */

      strcpy(str, "tuxpaint.cfg");
    }
#endif


  fi = fopen(str, "r");
  if (fi != NULL)
    {
      parse_options(fi);
      fclose(fi);
    }
  else
    debug(str);


  /* Handle command-line arguments: */

  for (i = 1; i < argc; i++)
    {
      if (strcmp(argv[i], "--fullscreen") == 0 || strcmp(argv[i], "-f") == 0)
	{
	  fullscreen = 1;
	}
      else if (strcmp(argv[i], "--windowed") == 0 || strcmp(argv[i], "-w") == 0)
	{
	  fullscreen = 0;
	}
      else if (strcmp(argv[i], "--mirrorstamps") == 0)
	{
	  mirrorstamps = 1;
	}
      else if (strcmp(argv[i], "--dontmirrorstamps") == 0)
	{
	  mirrorstamps = 0;
	}
      else if (strcmp(argv[i], "--nostampcontrols") == 0)
	{
	  disable_stamp_controls = 1;
	}
      else if (strcmp(argv[i], "--stampcontrols") == 0)
	{
	  disable_stamp_controls = 0;
	}
      else if (strcmp(argv[i], "--noshortcuts") == 0)
	{
	  noshortcuts = 1;
	}
      else if (strcmp(argv[i], "--shortcuts") == 0)
	{
	  noshortcuts = 0;
	}
      else if ( argv[i][0]=='-' && argv[i][1]=='-' && argv[i][2]>='1' && argv[i][2]<='9' )
	{
	  char *endp1;
	  char *endp2;
	  int w,h;
	  w = strtoul(argv[i]+2, &endp1, 10);
	  h = strtoul(endp1+1,   &endp2, 10);
	  // sanity check it
	  if(argv[i]+2==endp1 || endp1+1==endp2 || *endp1!='x' || *endp2 || w<500 || h<480 || h>w*3 || w>h*4)
	    {
	      show_usage(stderr, (char *) getfilename(argv[0]));
	      exit(1);
	    }
	  WINDOW_WIDTH  = w;
	  WINDOW_HEIGHT = h;
	}
      else if (strcmp(argv[i], "--nooutlines") == 0)
	{
	  dont_do_xor = 1;
	}
      else if (strcmp(argv[i], "--outlines") == 0)
	{
	  dont_do_xor = 0;
	}
      else if (strcmp(argv[i], "--keyboard") == 0)
	{
	  keymouse = 1;
	}
      else if (strcmp(argv[i], "--mouse") == 0)
	{
	  keymouse = 0;
	}
      else if (strcmp(argv[i], "--nowheelmouse") == 0)
	{
	  wheely = 0;
	}
      else if (strcmp(argv[i], "--wheelmouse") == 0)
	{
	  wheely = 1;
	}
      else if (strcmp(argv[i], "--grab") == 0)
	{
	  grab_input = 1;
	}
      else if (strcmp(argv[i], "--dontgrab") == 0)
	{
	  grab_input = 0;
	}
      else if (strcmp(argv[i], "--nofancycursors") == 0)
	{
	  no_fancy_cursors = 1;
	}
      else if (strcmp(argv[i], "--fancycursors") == 0)
	{
	  no_fancy_cursors = 0;
	}
      else if (strcmp(argv[i], "--saveover") == 0)
	{
	  promptless_save = SAVE_OVER_ALWAYS;
	}
      else if (strcmp(argv[i], "--saveoverask") == 0)
	{
	  promptless_save = SAVE_OVER_PROMPT;
	}
      else if (strcmp(argv[i], "--saveovernew") == 0)
	{
	  promptless_save = SAVE_OVER_NO;
	}
      else if (strcmp(argv[i], "--altprintnever") == 0)
	{
	  alt_print_command_default = ALTPRINT_NEVER;
	}
      else if (strcmp(argv[i], "--altprintalways") == 0)
	{
	  alt_print_command_default = ALTPRINT_ALWAYS;
	}
      else if (strcmp(argv[i], "--altprintmod") == 0)
	{
	  alt_print_command_default = ALTPRINT_MOD;
	}
      else if (strcmp(argv[i], "--uppercase") == 0 || strcmp(argv[i], "-u") == 0)
	{
	  only_uppercase = 1;
	}
      else if (strcmp(argv[i], "--mixedcase") == 0 || strcmp(argv[i], "-m") == 0)
	{
	  only_uppercase = 0;
	}
      else if (strcmp(argv[i], "--simpleshapes") == 0 ||
	       strcmp(argv[i], "-s") == 0)
	{
	  simple_shapes = 1;
	}
      else if (strcmp(argv[i], "--complexshapes") == 0)
	{
	  simple_shapes = 0;
	}
      else if (strcmp(argv[i], "--noquit") == 0 || strcmp(argv[i], "-x") == 0)
	{
	  disable_quit = 1;
	}
      else if (strcmp(argv[i], "--quit") == 0)
	{
	  disable_quit = 0;
	}
      else if (strcmp(argv[i], "--nosave") == 0)
	{
	  disable_save = 1;
	}
      else if (strcmp(argv[i], "--save") == 0)
	{
	  disable_save = 0;
	}
      else if (strcmp(argv[i], "--nostamps") == 0)
	{
	  dont_load_stamps = 1;
	}
      else if (strcmp(argv[i], "--stamps") == 0)
	{
	  dont_load_stamps = 0;
	}
      else if (strcmp(argv[i], "--nosysfonts") == 0)
	{
	  no_system_fonts = 1;
	}
      else if (strcmp(argv[i], "--nobuttondistinction") == 0)
	{
	  no_button_distinction = 1;
	}
      else if (strcmp(argv[i], "--sysfonts") == 0)
	{
	  no_system_fonts = 0;
	}
      else if (strcmp(argv[i], "--noprint") == 0 || strcmp(argv[i], "-p") == 0)
	{
	  disable_print = 1;
	}
      else if (strcmp(argv[i], "--print") == 0)
	{
	  disable_print = 0;
	}
      else if (strcmp(argv[i], "--noprintcfg") == 0)
	{
#ifndef WIN32
	  fprintf(stderr, "Note: printcfg option only applies to Windows!\n");
#endif
	  use_print_config = 0;
	}
      else if (strcmp(argv[i], "--printcfg") == 0)
	{
#ifndef WIN32
	  fprintf(stderr, "Note: printcfg option only applies to Windows!\n");
#endif
	  use_print_config = 1;
	}
      else if (strstr(argv[i], "--printdelay=") == argv[i])
	{
	  sscanf(strstr(argv[i], "--printdelay=") + 13, "%d", &print_delay);
#ifdef DEBUG
	  printf("Print delay set to %d seconds\n", print_delay);
#endif
	}
      else if (strcmp(argv[i], "--nosound") == 0 || strcmp(argv[i], "-q") == 0)
	{
	  use_sound = 0;
	}
      else if (strcmp(argv[i], "--sound") == 0)
	{
	  use_sound = 1;
	}
      else if (strcmp(argv[i], "--locale") == 0 || strcmp(argv[i], "-L") == 0)
	{
	  if (i < argc - 1)
	    {
	      do_locale_option(argv[++i]);
	    }
	  else
	    {
	      /* Forgot to specify the language (locale)! */
	
	      fprintf(stderr, "%s takes an argument\n", argv[i]);
	      show_locale_usage(stderr, (char *) getfilename(argv[0]));
	      exit(1);
	    }
	}
      else if (strstr(argv[i], "--lang=") == argv[i])
	{
	  set_langstr(argv[i] + 7);
	}
      else if (strcmp(argv[i], "--lang") == 0 || strcmp(argv[i], "-l") == 0)
	{
	  if (i < argc - 1)
	    {
	      set_langstr(argv[i + 1]);
	      i++;
	    }
	  else
	    {
	      /* Forgot to specify the language! */
	
	      fprintf(stderr, "%s takes an argument\n", argv[i]);
	      show_lang_usage(stderr, (char *) getfilename(argv[0]));
	      exit(1);
	    }
	}
      else if (strcmp(argv[i], "--savedir") == 0)
	{
	  if (i < argc - 1)
	    {
	      if (savedir != NULL)
		free(savedir);

	      savedir = strdup(argv[i + 1]);
	      remove_slash(savedir);
	      i++;
	    }
	  else
	    {
	      /* Forgot to specify the directory name! */

	      fprintf(stderr, "%s takes an argument\n", argv[i]);
	      show_usage(stderr, (char *) getfilename(argv[0]));
	      exit(1);
	    }
	}
      else if (strcmp(argv[i], "--record") == 0 ||
	       strcmp(argv[i], "--playback") == 0)
	{
	  if (i < argc - 1)
	    {
	      playfile = strdup(argv[i + 1]);

	      if (strcmp(argv[i], "--record") == 0)
		recording = 1;
	      else if (strcmp(argv[i], "--playback") == 0)
		playing = 1;
	
	      i++;
	    }
	  else
	    {
	      /* Forgot to specify the filename! */

	      fprintf(stderr, "%s takes an argument\n", argv[i]);
	      show_usage(stderr, (char *) getfilename(argv[0]));
	      exit(1);
	    }
	}
      else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0)
	{
	  show_version();
	  exit(0);
	}
      else if (strcmp(argv[i], "--copying") == 0 || strcmp(argv[i], "-c") == 0)
	{
	  show_version();
	  printf(
		 "\n"
		 "This program is free software; you can redistribute it\n"
		 "and/or modify it under the terms of the GNU General Public\n"
		 "License as published by the Free Software Foundation;\n"
		 "either version 2 of the License, or (at your option) any\n"
		 "later version.\n"
		 "\n"
		 "This program is distributed in the hope that it will be\n"
		 "useful and entertaining, but WITHOUT ANY WARRANTY; without\n"
		 "even the implied warranty of MERCHANTABILITY or FITNESS\n"
		 "FOR A PARTICULAR PURPOSE.  See the GNU General Public\n"
		 "License for more details.\n"
		 "\n"
		 "You should have received a copy of the GNU General Public\n"
		 "License along with this program; if not, write to the Free\n"
		 "Software Foundation, Inc., 59 Temple Place, Suite 330,\n"
		 "Boston, MA  02111-1307  USA\n"
		 "\n"
		 );
	  exit(0);
	}
      else if (strcmp(argv[i], "--help") == 0 ||
	       strcmp(argv[i], "-h") == 0)
	{
	  show_version();
	  show_usage(stdout, (char *) getfilename(argv[0]));

	  printf(
		 "See: " DOC_PREFIX "README.txt\n"
		 "\n");
	  exit(0);
	}
      else if (strcmp(argv[i], "--usage") == 0 ||
	       strcmp(argv[i], "-u") == 0)
	{
	  show_usage(stdout, (char *) getfilename(argv[0]));
	  exit(0);
	}
      else if (strcmp(argv[i], "--nosysconfig") == 0)
	{
	  debug("Not using system config.");   
	}
      else if (strcmp(argv[i], "--nolockfile") == 0)
        {
	  debug("Not using lockfile");   
          ok_to_use_lockfile = 0;
        }
      else
	{
	  show_usage(stderr, (char *) getfilename(argv[0]));
	  exit(1);
	}
    }


  setup_language(getfilename(argv[0]));


#ifndef WIN32
  putenv((char *) "SDL_VIDEO_X11_WMCLASS=TuxPaint.TuxPaint");
#endif
  
  /* Test for lockfile, if we're using one: */

  if (ok_to_use_lockfile)
  {
    char * lock_fname;
    time_t time_lock, time_now;
    char * homedirdir;


    /* Get the current time: */
    
    time_now = time(NULL);
    

    /* Look for the lockfile... */

    lock_fname = get_fname("lockfile.dat");

    fi = fopen(lock_fname, "r");
    if (fi != NULL)
    {
      /* If it exists, read its contents: */

      if (fread(&time_lock, sizeof(time_t), 1, fi) > 0)
      {
	/* Has it not been 30 seconds yet? */

	if (time_now < time_lock + 30)
	{
          /* FIXME: Wrap in gettext() */
          printf("\nYou're already running a copy of Tux Paint!\n\n");

	  free(lock_fname);

	  fclose(fi);
          exit(0);
	}
      }

      fclose(fi);
    }


    /* Okay to run; create/update the lockfile */

    /* (Make sure the directory exists, first!) */
    homedirdir = get_fname("");
    mkdir(homedirdir, 0755);
    free(homedirdir);

  
    fi = fopen(lock_fname, "w");
    if (fi != NULL)
    {
      /* If we can write to it, do so! */

      fwrite(&time_now, sizeof(time_t), 1, fi);
      fclose(fi);
    }
    else
    {
      fprintf(stderr,
 	      "\nWarning: I couldn't create the lockfile (%s)\n"
	      "The error that occurred was:\n"
	      "%s\n\n", lock_fname, strerror(errno));
    }

    free(lock_fname);
  }


  init_flags = SDL_INIT_VIDEO|SDL_INIT_TIMER;
  if(use_sound)
    init_flags |= SDL_INIT_AUDIO;
  if(!fullscreen)
    init_flags |= SDL_INIT_NOPARACHUTE;  // allow debugger to catch crash

  /* Init SDL */
  if (SDL_Init(init_flags) < 0)
    {
#ifndef NOSOUND
      char *olderr = strdup(SDL_GetError());
      use_sound = 0;
      init_flags &= ~SDL_INIT_AUDIO;
      if (SDL_Init(init_flags) >= 0)
        {
          // worked, w/o sound
	  fprintf(stderr,
		  "\nWarning: I could not initialize audio!\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", olderr);
          free(olderr);
        }
      else
#endif
        {
          fprintf(stderr,
	          "\nError: I could not initialize video and/or the timer!\n"
	          "The Simple DirectMedia Layer error that occurred was:\n"
	          "%s\n\n", SDL_GetError());
          exit(1);
        }
    }

#ifndef NOSOUND
  // need Mix_OpenAudio(44100, AUDIO_S16, 2, 2048) for WIN32 ?
  if (use_sound && Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 1024) < 0)
    {
      fprintf(stderr,
	"\nWarning: I could not set up audio for 44100 Hz "
	"16-bit stereo.\n"
	"The Simple DirectMedia Layer error that occurred was:\n"
	"%s\n\n", SDL_GetError());
      use_sound = 0;
    }

  i = NUM_SOUNDS;
  while(use_sound && i--)
    {
      sounds[i] = Mix_LoadWAV(sound_fnames[i]);

      if (sounds[i] == NULL)
        {
          fprintf(stderr,
                  "\nWarning: I couldn't open a sound file:\n%s\n"
                  "The Simple DirectMedia Layer error that occurred was:\n"
                  "%s\n\n", sound_fnames[i], SDL_GetError());
          use_sound = 0;
        }
    }
#endif


  /* Set-up Key-Repeat: */
  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,
		      SDL_DEFAULT_REPEAT_INTERVAL);
 
  /* Init TTF stuff: */
  if (TTF_Init() < 0)
    {
      fprintf(stderr,
	      "\nError: I could not initialize the font (TTF) library!\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      SDL_Quit();
      exit(1);
    }



  setup_screen_layout();

  /* Set window icon and caption: */

#ifndef __APPLE__
  seticon();
#endif
  SDL_WM_SetCaption("Tux Paint", "Tux Paint");

  /* Open Window: */

  if (fullscreen)
    {
#ifdef USE_HWSURFACE
      screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT,
				VIDEO_BPP, SDL_FULLSCREEN | SDL_HWSURFACE);
#else
      screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT,
				VIDEO_BPP, SDL_FULLSCREEN | SDL_SWSURFACE);
#endif

      if (screen == NULL)
	{
	  fprintf(stderr,
		  "\nWarning: I could not open the display in fullscreen mode.\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", SDL_GetError());

	  fullscreen = 0;
	}
    }


  if (!fullscreen)
    {
#ifdef USE_HWSURFACE
      screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT,
		      		VIDEO_BPP, SDL_HWSURFACE);
#else
      screen = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT,
		      		VIDEO_BPP, SDL_SWSURFACE);
#endif
    }

  if (screen == NULL)
    {
      fprintf(stderr,
	      "\nError: I could not open the display.\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }


  ////////// quickly: title image, version, progress bar, and watch cursor

  img_title = loadimage(DATA_PREFIX "images/title.png");
  img_title_tuxpaint = loadimage(DATA_PREFIX "images/title-tuxpaint.png");
  img_title_credits = loadimage(DATA_PREFIX "images/title-credits.png");
  img_progress = loadimage(DATA_PREFIX "images/ui/progress.png");

  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 255, 255, 255));

  dest.x = ((WINDOW_WIDTH - img_title->w - (img_title_tuxpaint->w / 2)) / 2) + (img_title_tuxpaint->w / 2) + 20;
  dest.y = (WINDOW_HEIGHT - img_title->h);

  SDL_BlitSurface(img_title, NULL, screen, &dest);

  dest.x = 10;
  dest.y = (WINDOW_HEIGHT - img_title->h) + img_title_tuxpaint->h * 0.8 + 7;

  SDL_BlitSurface(img_title_tuxpaint, NULL, screen, &dest);

  dest.x = 10;
  dest.y = 5;

  SDL_BlitSurface(img_title_credits, NULL, screen, &dest);

  prog_bar_ctr = 0;
  show_progress_bar();

  SDL_Flip(screen);

  medium_font = TTF_OpenFont(DATA_PREFIX "fonts/default_font.ttf",
		      18 - (only_uppercase * 3));

  if (medium_font == NULL)
    {
      fprintf(stderr,
	      "\nError: Can't load font file: "
	      DATA_PREFIX "fonts/default_font.ttf\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }

  snprintf(tmp_str, sizeof(tmp_str), "Version: %s – %s", VER_VERSION, VER_DATE);
  tmp_surf = render_text(medium_font, tmp_str, black);
  dest.x = 10;
  dest.y = WINDOW_HEIGHT - img_progress->h - tmp_surf->h;
  SDL_BlitSurface(tmp_surf, NULL, screen, &dest);
  SDL_FreeSurface(tmp_surf);

#ifdef DEBUG
  printf("%s\n", tmp_str);
#endif
  
  snprintf(tmp_str, sizeof(tmp_str),
	   "© 2002–2005 Bill Kendrick et al.");
  tmp_surf = render_text(medium_font, tmp_str, black);
  dest.x = 10;
  dest.y = WINDOW_HEIGHT - img_progress->h - (tmp_surf->h * 2);
  SDL_BlitSurface(tmp_surf, NULL, screen, &dest);
  SDL_FreeSurface(tmp_surf);

  SDL_Flip(screen);


#if defined(WIN32) && defined(LARGE_CURSOR_FULLSCREEN_BUG)
  if (fullscreen && no_fancy_cursors == 0)
    {
      fprintf(stderr, "Warning: An SDL bug causes the fancy cursors to leave\n"
	      "trails in fullscreen mode.  Disabling fancy cursors.\n"
	      "(You can do this yourself with 'nofancycursors' option,\n"
	      "to avoid this warning in the future.)\n");
      no_fancy_cursors = 1;
    }
#endif


  /* Create cursors: */

  scale = 1;

#ifdef SMALL_CURSOR_SHAPES
  scale = 2;
#endif

  // this one first, because we need it yesterday
  cursor_watch = get_cursor(watch_bits, watch_mask_bits,
			    watch_width, watch_height,
			    14 / scale, 14 / scale);

  do_setcursor(cursor_watch);
  show_progress_bar();

#ifdef FORKED_FONTS
  reliable_write(font_socket_fd, &no_system_fonts, sizeof no_system_fonts);
#else
  font_thread = SDL_CreateThread(load_user_fonts, NULL);
#endif

  // continuing on with the rest of the cursors...

  cursor_hand = get_cursor(hand_bits, hand_mask_bits,
			   hand_width, hand_height,
			   12 / scale, 1 / scale);
  
  cursor_wand = get_cursor(wand_bits, wand_mask_bits,
		           wand_width, wand_height,
		           4 / scale, 4 / scale);
  
  cursor_insertion = get_cursor(insertion_bits, insertion_mask_bits,
		                insertion_width, insertion_height,
		                7 / scale, 4 / scale);
  
  cursor_brush = get_cursor(brush_bits, brush_mask_bits,
			    brush_width, brush_height,
			    4 / scale, 28 / scale);
  
  cursor_crosshair = get_cursor(crosshair_bits, crosshair_mask_bits,
				crosshair_width, crosshair_height,
				15 / scale, 15 / scale);
  
  cursor_rotate = get_cursor(rotate_bits, rotate_mask_bits,
		             rotate_width, rotate_height,
		             15 / scale, 15 / scale);
  
  cursor_arrow = get_cursor(arrow_bits, arrow_mask_bits,
			    arrow_width, arrow_height,
			    0, 0);

  cursor_up = get_cursor(up_bits, up_mask_bits,
			 up_width, up_height,
			 15 / scale, 1 / scale);

  cursor_down = get_cursor(down_bits, down_mask_bits,
			   down_width, down_height,
			   15 / scale, 30 / scale);

  cursor_tiny = get_cursor(tiny_bits, tiny_mask_bits,
			   tiny_width, tiny_height,
			   3, 3);   /* Exactly the same in SMALL (16x16) size! */



  /* Create drawing canvas: */

  canvas = SDL_CreateRGBSurface(screen->flags,
				WINDOW_WIDTH - (96 * 2),
				(48 * 7) + 40 + HEIGHTOFFSET,
				screen->format->BitsPerPixel,
				screen->format->Rmask,
				screen->format->Gmask,
				screen->format->Bmask,
				0);

  img_starter = NULL;
  img_starter_bkgd = NULL;
  starter_mirrored = 0;
  starter_flipped = 0;

  if (canvas == NULL)
    {
      fprintf(stderr, "\nError: Can't build drawing canvas!\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }

  SDL_FillRect(canvas, NULL, SDL_MapRGB(canvas->format, 255, 255, 255));


  /* Create undo buffer space: */

  for (i = 0; i < NUM_UNDO_BUFS; i++)
    {
      undo_bufs[i] = SDL_CreateRGBSurface(screen->flags,
					  WINDOW_WIDTH - (96 * 2),
					  (48 * 7) + 40 + HEIGHTOFFSET,
					  screen->format->BitsPerPixel,
					  screen->format->Rmask,
					  screen->format->Gmask,
					  screen->format->Bmask,
					  0);


      if (undo_bufs[i] == NULL)
	{
	  fprintf(stderr, "\nError: Can't build undo buffer! (%d of %d)\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", i + 1, NUM_UNDO_BUFS, SDL_GetError());
	
	  cleanup();
	  exit(1);
	}

      undo_starters[i] = UNDO_STARTER_NONE;
    }


 
  /* Load other images: */

  for (i = 0; i < NUM_TOOLS; i++)
    img_tools[i] = loadimage(tool_img_fnames[i]);

  img_title_on = loadimage(DATA_PREFIX "images/ui/title.png");
  img_title_large_on = loadimage(DATA_PREFIX "images/ui/title_large.png");
  img_title_off = loadimage(DATA_PREFIX "images/ui/no_title.png");
  img_title_large_off = loadimage(DATA_PREFIX "images/ui/no_title_large.png");
  
  img_btn_up = loadimage(DATA_PREFIX "images/ui/btn_up.png");
  img_btn_down = loadimage(DATA_PREFIX "images/ui/btn_down.png");
  img_btn_off = loadimage(DATA_PREFIX "images/ui/btn_off.png");

  img_black = SDL_CreateRGBSurface(SDL_SRCALPHA|SDL_SWSURFACE,
					       img_btn_off->w, img_btn_off->h,
					       img_btn_off->format->BitsPerPixel,
					       img_btn_off->format->Rmask,
					       img_btn_off->format->Gmask,
					       img_btn_off->format->Bmask,
					       img_btn_off->format->Amask);
  SDL_FillRect(img_black, NULL, SDL_MapRGBA(screen->format, 0, 0, 0, 255));

  img_grey = SDL_CreateRGBSurface(SDL_SRCALPHA|SDL_SWSURFACE,
					       img_btn_off->w, img_btn_off->h,
					       img_btn_off->format->BitsPerPixel,
					       img_btn_off->format->Rmask,
					       img_btn_off->format->Gmask,
					       img_btn_off->format->Bmask,
					       img_btn_off->format->Amask);
  SDL_FillRect(img_grey, NULL, SDL_MapRGBA(screen->format, 0x88, 0x88, 0x88, 255));

  show_progress_bar();

  img_yes = loadimage(DATA_PREFIX "images/ui/yes.png");
  img_no = loadimage(DATA_PREFIX "images/ui/no.png");

  img_open = loadimage(DATA_PREFIX "images/ui/open.png");
  img_erase = loadimage(DATA_PREFIX "images/ui/erase.png");
  img_back = loadimage(DATA_PREFIX "images/ui/back.png");
  img_trash = loadimage(DATA_PREFIX "images/ui/trash.png");
  
  img_popup_arrow = loadimage(DATA_PREFIX "images/ui/popup_arrow.png");
  
  img_dead40x40 = loadimage(DATA_PREFIX "images/ui/dead40x40.png");

  img_printer = loadimage(DATA_PREFIX "images/ui/printer.png");
  img_printer_wait = loadimage(DATA_PREFIX "images/ui/printer_wait.png");

  img_save_over = loadimage(DATA_PREFIX "images/ui/save_over.png");

  img_grow = loadimage(DATA_PREFIX "images/ui/grow.png");
  img_shrink = loadimage(DATA_PREFIX "images/ui/shrink.png");
  
  img_bold = loadimage(DATA_PREFIX "images/ui/bold.png");
  img_italic = loadimage(DATA_PREFIX "images/ui/italic.png");

  show_progress_bar();

  tmp_imgcurup    = loadimage(DATA_PREFIX "images/ui/cursor_up_large.png");
  tmp_imgcurdown  = loadimage(DATA_PREFIX "images/ui/cursor_down_large.png");
  img_cursor_up   = thumbnail(tmp_imgcurup,   THUMB_W, THUMB_H, 0);
  img_cursor_down = thumbnail(tmp_imgcurdown, THUMB_W, THUMB_H, 0);

  tmp_imgcurup    = loadimage(DATA_PREFIX "images/ui/cursor_starter_up.png");
  tmp_imgcurdown  = loadimage(DATA_PREFIX "images/ui/cursor_starter_down.png");
  img_cursor_starter_up   = thumbnail(tmp_imgcurup,   THUMB_W, THUMB_H, 0);
  img_cursor_starter_down = thumbnail(tmp_imgcurdown, THUMB_W, THUMB_H, 0);
  SDL_FreeSurface(tmp_imgcurup);
  SDL_FreeSurface(tmp_imgcurdown);

  show_progress_bar();

  img_scroll_up = loadimage(DATA_PREFIX "images/ui/scroll_up.png");
  img_scroll_down = loadimage(DATA_PREFIX "images/ui/scroll_down.png");

  img_scroll_up_off = loadimage(DATA_PREFIX "images/ui/scroll_up_off.png");
  img_scroll_down_off = loadimage(DATA_PREFIX "images/ui/scroll_down_off.png");

#ifdef LOW_QUALITY_COLOR_SELECTOR
  img_paintcan = loadimage(DATA_PREFIX "images/ui/paintcan.png");
#endif

  show_progress_bar();

  img_sparkles = loadimage(DATA_PREFIX "images/ui/sparkles.png");
  img_grass    = loadimage(DATA_PREFIX "images/ui/grass.png");


  /* Load brushes: */
  load_brush_dir(DATA_PREFIX "brushes");
  homedirdir = get_fname("brushes");
  load_brush_dir(homedirdir);
#ifdef WIN32
  free(homedirdir);
  homedirdir = get_fname("data/brushes");
  load_brush_dir(homedirdir);
#endif

  if (num_brushes == 0)
    {
      fprintf(stderr,
	      "\nError: No brushes found in " DATA_PREFIX "brushes/\n"
	      "or %s\n\n", homedirdir);
      cleanup();
      exit(1);
    }

  free(homedirdir);
  

  /* Load system fonts: */

  large_font = TTF_OpenFont(DATA_PREFIX "fonts/default_font.ttf",
			    36 - (only_uppercase * 3));

  if (large_font == NULL)
    {
      fprintf(stderr,
	      "\nError: Can't load font file: "
	      DATA_PREFIX "fonts/default_font.ttf\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }


  small_font = TTF_OpenFont(DATA_PREFIX "fonts/default_font.ttf",
		            14 - (only_uppercase * 2));

  if (small_font == NULL)
    {
      fprintf(stderr,
	      "\nError: Can't load font file: "
	      DATA_PREFIX "fonts/default_font.ttf\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }


  locale_font = load_locale_font(medium_font,18);

#if 0
  // put elsewhere for THREADED_FONTS
  /* Load user fonts, for the text tool */
  load_user_fonts();
#endif

  if (!dont_load_stamps)
    load_stamps();


  /* Load magic icons: */
  for (i = 0; i < NUM_MAGICS; i++)
    img_magics[i] = loadimage(magic_img_fnames[i]);

  show_progress_bar();

  /* Load shape icons: */
  for (i = 0; i < NUM_SHAPES; i++)
    img_shapes[i] = loadimage(shape_img_fnames[i]);

  show_progress_bar();

  /* Load tip tux images: */
  for (i = 0; i < NUM_TIP_TUX; i++)
    img_tux[i] = loadimage(tux_img_fnames[i]);

  show_progress_bar();

  img_mouse = loadimage(DATA_PREFIX "images/ui/mouse.png");
  img_mouse_click = loadimage(DATA_PREFIX "images/ui/mouse_click.png");

  show_progress_bar();
  
  /* Create toolbox and selector labels: */

  for (i = 0; i < NUM_TITLES; i++)
    {
      if (strlen(title_names[i]) > 0)
	{
	  TTF_Font * myfont = large_font;
          char *td_str = textdir(gettext(title_names[i]));
	  
	  if (need_own_font && strcmp(gettext(title_names[i]), title_names[i]))
	    myfont = locale_font;
          upstr = uppercase(td_str);
          free(td_str);
          tmp_surf = render_text(myfont, upstr, black);
          free(upstr);
	  img_title_names[i] = thumbnail(tmp_surf, min(84, tmp_surf->w), tmp_surf->h, 0);
	  SDL_FreeSurface(tmp_surf);
	}
      else
	{
	  img_title_names[i] = NULL;
	}
    }



  /* Generate color selection buttons: */

#ifndef LOW_QUALITY_COLOR_SELECTOR

  /* Create appropriately-shaped buttons: */
  img1 = loadimage(DATA_PREFIX "images/ui/paintwell.png");
  img2 = thumbnail(img1,        color_button_w, color_button_h, 0);
  tmp_btn_up        = thumbnail(img_btn_up,  color_button_w, color_button_h, 0);
  tmp_btn_down      = thumbnail(img_btn_down,color_button_w, color_button_h, 0);
  img_color_btn_off = thumbnail(img_btn_off, color_button_w, color_button_h, 0);
  SDL_FreeSurface(img1);

  /* Create surfaces to draw them into: */

  for (i = 0; i < NUM_COLORS*2; i++)
    {

      img_color_btns[i] = SDL_CreateRGBSurface(screen->flags,
					       /* (WINDOW_WIDTH - 96) / NUM_COLORS, 48, */
					       tmp_btn_up->w, tmp_btn_up->h,
					       screen->format->BitsPerPixel,
					       screen->format->Rmask,
					       screen->format->Gmask,
					       screen->format->Bmask,
					       0);

      if (img_color_btns[i] == NULL)
	{
	  fprintf(stderr, "\nError: Can't build color button!\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", SDL_GetError());

	  cleanup();
	  exit(1);
	}

      SDL_LockSurface(img_color_btns[i]);
    }


  /* Generate the buttons based on the thumbnails: */

  SDL_LockSurface(tmp_btn_down);
  SDL_LockSurface(tmp_btn_up);

  getpixel_tmp_btn_up = getpixels[tmp_btn_up->format->BytesPerPixel];
  getpixel_tmp_btn_down = getpixels[tmp_btn_down->format->BytesPerPixel];
  getpixel_img2 = getpixels[img2->format->BytesPerPixel];
  

  for (y = 0; y < tmp_btn_up->h /* 48 */; y++)
    {
      for (x = 0; x < tmp_btn_up->w /* (WINDOW_WIDTH - 96) / NUM_COLORS */; x++)
	{
	  double ru, gu, bu, rd, gd, bd, aa;
	  Uint8 a;

	  SDL_GetRGB(getpixel_tmp_btn_up(tmp_btn_up, x, y), tmp_btn_up->format, &r, &g, &b);
	  
	  ru = sRGB_to_linear_table[r];
	  gu = sRGB_to_linear_table[g];
	  bu = sRGB_to_linear_table[b];
	  SDL_GetRGB(getpixel_tmp_btn_down(tmp_btn_down, x, y), tmp_btn_down->format, &r, &g, &b);
	  rd = sRGB_to_linear_table[r];
	  gd = sRGB_to_linear_table[g];
	  bd = sRGB_to_linear_table[b];
	  SDL_GetRGBA(getpixel_img2(img2, x, y), img2->format, &r, &g, &b, &a);
	  aa = a/255.0;

	  for (i = 0; i < NUM_COLORS; i++)
	    {
	      double rh = sRGB_to_linear_table[color_hexes[i][0]];
	      double gh = sRGB_to_linear_table[color_hexes[i][1]];
	      double bh = sRGB_to_linear_table[color_hexes[i][2]];
	      putpixels[img_color_btns[i]->format->BytesPerPixel]
		       (img_color_btns[i], x, y,
		       SDL_MapRGB(img_color_btns[i]->format,
				  linear_to_sRGB(rh*aa + ru*(1.0-aa)),
				  linear_to_sRGB(gh*aa + gu*(1.0-aa)),
				  linear_to_sRGB(bh*aa + bu*(1.0-aa))));
	      putpixels[img_color_btns[i]->format->BytesPerPixel]
	               (img_color_btns[i+NUM_COLORS], x, y,
		       SDL_MapRGB(img_color_btns[i+NUM_COLORS]->format,
				  linear_to_sRGB(rh*aa + rd*(1.0-aa)),
				  linear_to_sRGB(gh*aa + gd*(1.0-aa)),
				  linear_to_sRGB(bh*aa + bd*(1.0-aa))));
	    }
	}
    }

  for (i = 0; i < NUM_COLORS*2; i++)
    SDL_UnlockSurface(img_color_btns[i]);

  SDL_UnlockSurface(tmp_btn_up);
  SDL_UnlockSurface(tmp_btn_down);
  SDL_FreeSurface(tmp_btn_up);
  SDL_FreeSurface(tmp_btn_down);
  SDL_FreeSurface(img2);

#endif

  create_button_labels();


  /* Seed random-number generator: */

  srand(SDL_GetTicks());


  /* Enable Unicode support in SDL: */
  
  SDL_EnableUNICODE(1);
  
#ifndef WIN32  
  /* Set up signal handler for SIGPIPE (in case printer command dies;
     e.g., altprintcommand=kprinter, but 'Cancel' is clicked,
     instead of 'Ok') */
  
  signal(SIGPIPE, signal_handler);
#endif
  
  /* Open demo recording or playback file: */

  if (recording)
    {
      demofi = fopen(playfile, "w");

      if (demofi == NULL)
	{
	  fprintf(stderr, "Error: Cannot create recording file: %s\n"
		  "%s\n\n",
		  playfile, strerror(errno));
	  exit(1);
	}
    }
  else if (playing)
    {
      demofi = fopen(playfile, "r");
    
      if (demofi == NULL)
	{
	  fprintf(stderr, "Error: Cannot open playback file: %s\n"
		  "%s\n\n",
		  playfile, strerror(errno));
	  exit(1);
	}
    }
  else
    {
      demofi = NULL;
    }
}

#ifndef WIN32
void signal_handler(int sig)
{
  sig = sig;
  /*
  if (sig == SIGPIPE)
    fprintf(stderr, "SIGPIPE!\n");
  */
}
#endif

/* Render a button label using the appropriate string/font: */
static SDL_Surface * do_render_button_label(const char * const label)
{
  SDL_Surface * tmp_surf, * surf;
  SDL_Color black = {0, 0, 0, 0};
  TTF_Font * myfont = small_font;
  char *td_str = textdir(gettext(label));
  char *upstr = uppercase(td_str);

  free(td_str);

  if (need_own_font && strcmp(gettext(label), label))
    myfont = locale_font;
  tmp_surf = render_text(myfont, upstr, black);
  free(upstr);
  surf = thumbnail(tmp_surf, min(48, tmp_surf->w), tmp_surf->h, 0);
  SDL_FreeSurface(tmp_surf);

  return surf;
}

static void create_button_labels(void)
{
  int i;

  for (i = 0; i < NUM_TOOLS; i++)
    img_tool_names[i] = do_render_button_label(tool_names[i]);

  for (i = 0; i < NUM_MAGICS; i++)
    img_magic_names[i] = do_render_button_label(magic_names[i]);

  for (i = 0; i < NUM_SHAPES; i++)
    img_shape_names[i] = do_render_button_label(shape_names[i]);

  // buttons for the file open dialog
  img_openlabels_open = do_render_button_label(gettext_noop("Open"));
  img_openlabels_erase = do_render_button_label(gettext_noop("Erase"));
  img_openlabels_back = do_render_button_label(gettext_noop("Back"));
}


static void seticon(void)
{
  int masklen;
  Uint8 * mask;
  SDL_Surface * icon;

  /* Load icon into a surface: */

#ifndef WIN32
  icon = IMG_Load(DATA_PREFIX "images/icon.png");
#else
  icon = IMG_Load(DATA_PREFIX "images/icon32x32.png");
#endif

  if (icon == NULL)
    {
      fprintf(stderr,
	      "\nWarning: I could not load the icon image: %s\n"
	      "The Simple DirectMedia error that occurred was:\n"
	      "%s\n\n", DATA_PREFIX "images/icon.png", SDL_GetError());
      return;
    }


#ifndef WIN32
  /* Create mask: */
  masklen = (((icon -> w) + 7) / 8) * (icon -> h);
  mask = malloc(masklen * sizeof(Uint8));
  memset(mask, 0xFF, masklen);

  /* Set icon: */
  SDL_WM_SetIcon(icon, mask);

  /* Free icon surface & mask: */
  free(mask);
#else
  /* Set icon: */
  SDL_WM_SetIcon(icon, NULL);
#endif
  SDL_FreeSurface(icon);


  /* Grab keyboard and mouse, if requested: */

  if (grab_input)
    {
      debug("Grabbing input!");
      SDL_WM_GrabInput(SDL_GRAB_ON);
    }
}


/* Load a mouse pointer (cursor) shape: */

static SDL_Cursor * get_cursor(unsigned char * bits, unsigned char * mask_bits,
			       unsigned int width, unsigned int height,
			       unsigned int x, unsigned int y)
{
  Uint8 b;
  Uint8 temp_bitmap[128], temp_bitmask[128];
  unsigned int i;


  if (((width + 7) / 8) * height > 128)
    {
      fprintf(stderr, "Error: Cursor is too large!\n");
      cleanup();
      exit(1);
    }

  for (i = 0; i < ((width + 7) / 8) * height; i++)
    {
      b = bits[i];

      temp_bitmap[i] = (((b & 0x01) << 7) |
			((b & 0x02) << 5) |
			((b & 0x04) << 3) |
			((b & 0x08) << 1) |
			((b & 0x10) >> 1) |
			((b & 0x20) >> 3) |
			((b & 0x40) >> 5) |
			((b & 0x80) >> 7));
    
      b = mask_bits[i];

      temp_bitmask[i] = (((b & 0x01) << 7) |
			 ((b & 0x02) << 5) |
			 ((b & 0x04) << 3) |
			 ((b & 0x08) << 1) |
			 ((b & 0x10) >> 1) |
			 ((b & 0x20) >> 3) |
			 ((b & 0x40) >> 5) |
			 ((b & 0x80) >> 7));
    }

  return(SDL_CreateCursor(temp_bitmap, temp_bitmask, width, height, x, y));
}


/* Load an image (with errors): */

static SDL_Surface * loadimage(const char * const fname)
{
  return(do_loadimage(fname, 1));
}


/* Load an image: */

static SDL_Surface * do_loadimage(const char * const fname, int abort_on_error)
{
  SDL_Surface * s, * disp_fmt_s;


  /* Load the image file: */

  s = IMG_Load(fname);
  if (s == NULL)
    {
      if (abort_on_error)
	{
	  fprintf(stderr,
		  "\nError: I couldn't load a graphics file:\n"
		  "%s\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", fname, SDL_GetError());

	  cleanup();
	  exit(1);
	}
      else
	{
	  return(NULL);
	}
    }


  /* Convert to the display format: */

  disp_fmt_s = SDL_DisplayFormatAlpha(s);
  if (disp_fmt_s == NULL)
    {
      if (abort_on_error)
	{
	  fprintf(stderr,
		  "\nError: I couldn't convert a graphics file:\n"
		  "%s\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", fname, SDL_GetError());

	  cleanup();
	  exit(1);
	}
      else
	{
	  SDL_FreeSurface(s);
	  return(NULL);
	}
    }


  /* Free the temp. surface & return the converted one: */

  SDL_FreeSurface(s);

  return(disp_fmt_s);
}


/* Draw the toolbar: */

static void draw_toolbar(void)
{
  int i;
  SDL_Rect dest;


  /* FIXME: Only allow print if we have something to print! */


  draw_image_title(TITLE_TOOLS, r_ttools);

  for (i = 0; i < NUM_TOOLS + TOOLOFFSET; i++)
    {
      dest.x = ((i % 2) * 48);
      dest.y = ((i / 2) * 48) + 40;


      if (i < NUM_TOOLS)
	{
	  SDL_Surface *button_color;
	  SDL_Surface *button_body;
	  if (i == cur_tool)
	    {
	      button_body  = img_btn_down;
	      button_color = img_black;
	    }
	  else if (tool_avail[i])
	    {
	      button_body  = img_btn_up;
	      button_color = img_black;
	    }
	  else
	    {
	      button_body  = img_btn_off;
	      button_color = img_grey;
	    }
          SDL_BlitSurface(button_body,  NULL, screen, &dest);
          SDL_BlitSurface(button_color, NULL, img_tools[i], NULL);
          SDL_BlitSurface(button_color, NULL, img_tool_names[i], NULL);

	  dest.x = ((i % 2) * 48) + 4;
	  dest.y = ((i / 2) * 48) + 40 + 4;

	  SDL_BlitSurface(img_tools[i], NULL, screen, &dest);


	  dest.x = ((i % 2) * 48) + 4 + (40 - img_tool_names[i]->w) / 2;
	  dest.y = ((i / 2) * 48) + 40 + 4 + (44 - img_tool_names[i]->h);

	  SDL_BlitSurface(img_tool_names[i], NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	}
    }
}


/* Draw magic controls: */

static void draw_magic(void)
{
  int magic, i, max, off_y;
  SDL_Rect dest;

  // restore these to black (stamp and text controls borrow them)
  SDL_BlitSurface(img_black, NULL, img_magics[MAGIC_FLIP], NULL);
  SDL_BlitSurface(img_black, NULL, img_magics[MAGIC_MIRROR], NULL);

  /* FIXME: Should we worry about more than 14 magic effects? :^/ */


  draw_image_title(TITLE_MAGIC, r_ttoolopt);

  if (NUM_MAGICS > 14 + TOOLOFFSET)
    {
      off_y = 24;
      max = 12 + TOOLOFFSET;

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40;

      if (magic_scroll > 0)
	{
	  SDL_BlitSurface(img_scroll_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_up_off, NULL, screen, &dest);
	}

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + 24 + ((6 + TOOLOFFSET / 2) * 48);

      if (magic_scroll < NUM_MAGICS - 12 - TOOLOFFSET)
	{
	  SDL_BlitSurface(img_scroll_down, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_down_off, NULL, screen, &dest);
	}
    }
  else
    {
      off_y = 0;
      max = 14 + TOOLOFFSET;
    }

  
  for (magic = magic_scroll;
       magic < magic_scroll + max;
       magic++)
    {
      i = magic - magic_scroll;
      
      dest.x = ((i % 2) * 48) + (WINDOW_WIDTH - 96);
      dest.y = ((i / 2) * 48) + 40 + off_y;

      if (magic < NUM_MAGICS)
	{
	  if (magic == cur_magic)
	    {
	      SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
	    }
	  else
	    {
	      SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
	    }
	
	  dest.x = WINDOW_WIDTH - 96 + ((i % 2) * 48) + 4;
	  dest.y = ((i / 2) * 48) + 40 + 4 + off_y;

	  SDL_BlitSurface(img_magics[magic], NULL, screen, &dest);

	
	  dest.x = WINDOW_WIDTH - 96 + ((i % 2) * 48) + 4 +
	    (40 - img_magic_names[magic]->w) / 2;
	  dest.y = (((i / 2) * 48) + 40 + 4 +
		    (44 - img_magic_names[magic]->h) + off_y);

	  SDL_BlitSurface(img_magic_names[magic], NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);	
	}
    }
}


/* Draw color selector: */

static unsigned colors_state = COLORSEL_ENABLE | COLORSEL_CLOBBER;

static unsigned draw_colors(unsigned action)
{
  unsigned i;
  SDL_Rect dest;
  static unsigned old_color;

  unsigned old_colors_state = colors_state;
  if (action==COLORSEL_CLOBBER)
    colors_state |= COLORSEL_CLOBBER;
  if (action==COLORSEL_REFRESH)
    colors_state &= ~COLORSEL_CLOBBER;
  if (action==COLORSEL_DISABLE)
    colors_state = COLORSEL_DISABLE;
  if (action==COLORSEL_ENABLE)
    colors_state = COLORSEL_ENABLE;

  colors_are_selectable = colors_state == COLORSEL_ENABLE;

  if (colors_state&COLORSEL_CLOBBER)
    return old_colors_state;
  if (cur_color==old_color && colors_state==old_colors_state)
    return old_colors_state;
  old_color = cur_color;

  for (i = 0; i < NUM_COLORS; i++)
    {
      dest.x = r_colors.x + i%gd_colors.cols*color_button_w;
      dest.y = r_colors.y + i/gd_colors.cols*color_button_h;
#ifndef LOW_QUALITY_COLOR_SELECTOR
      SDL_BlitSurface(
        (colors_state == COLORSEL_ENABLE)
          ? img_color_btns[i + (i==cur_color)*NUM_COLORS]
          : img_color_btn_off,
        NULL,
        screen,
        &dest
      );
#else
      dest.w = color_button_w;
      dest.h = color_button_h;

      if (colors_state == COLORSEL_ENABLE)
	SDL_FillRect(screen, &dest,
		     SDL_MapRGB(screen->format,
				color_hexes[i][0],
				color_hexes[i][1],
				color_hexes[i][2]));
      else
	SDL_FillRect(screen, &dest,
		     SDL_MapRGB(screen->format, 240, 240, 240));
      if (i==cur_color && colors_state==COLORSEL_ENABLE)
	{
	  dest.y += 4;
	  SDL_BlitSurface(img_paintcan, NULL, screen, &dest);
	}
#endif

    }
  update_screen_rect(&r_colors);

  // if only the color changed, no need to draw the title
  if (colors_state==old_colors_state)
    return old_colors_state;

  if (colors_state == COLORSEL_ENABLE)
    {
      SDL_BlitSurface(img_title_large_on, NULL, screen, &r_tcolors);
    
      dest.x = r_tcolors.x + (r_tcolors.w - img_title_names[TITLE_COLORS]->w) / 2;
      dest.y = r_tcolors.y + (r_tcolors.h - img_title_names[TITLE_COLORS]->h) / 2;
      SDL_BlitSurface(img_title_names[TITLE_COLORS], NULL, screen, &dest);
    }
  else
    {
      SDL_BlitSurface(img_title_large_off, NULL, screen, &r_tcolors);
    }

  update_screen_rect(&r_tcolors);
  return old_colors_state;
}


/* Draw brushes: */

static void draw_brushes(void)
{
  int i, off_y, max, brush;
  SDL_Rect dest;


  /* Draw the title: */
  draw_image_title(TITLE_BRUSHES, r_ttoolopt);


  /* Do we need scrollbars? */

  if (num_brushes > 14 + TOOLOFFSET)
    {
      off_y = 24;
      max = 12 + TOOLOFFSET;

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40;

      if (brush_scroll > 0)
	{
	  SDL_BlitSurface(img_scroll_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_up_off, NULL, screen, &dest);
	}

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + 24 + ((6 + TOOLOFFSET / 2) * 48);

      if (brush_scroll < num_brushes - 12 - TOOLOFFSET)
	{
	  SDL_BlitSurface(img_scroll_down, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_down_off, NULL, screen, &dest);
	}
    }
  else
    {
      off_y = 0;
      max = 14 + TOOLOFFSET;
    }


  /* Draw each of the shown brushes: */

  for (brush = brush_scroll;
       brush < brush_scroll + max;
       brush++)
    {
      i = brush - brush_scroll;


      dest.x = ((i % 2) * 48) + (WINDOW_WIDTH - 96);
      dest.y = ((i / 2) * 48) + 40 + off_y;

      if (brush == cur_brush)
	{
	  SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
	}
      else if (brush < num_brushes)
	{
	  SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	}

      if (brush < num_brushes)
	{
	  dest.x = ((i % 2) * 48) + (WINDOW_WIDTH - 96) +
	    ((48 - (img_brushes[brush]->w)) / 2);

	  /* FIXME: Shouldn't that be ->h??? */

	  dest.y = ((i / 2) * 48) + 40 + ((48 - (img_brushes[brush]->w)) / 2) +
	    off_y;

	  SDL_BlitSurface(img_brushes[brush], NULL, screen, &dest);
	}
    }
}


/* Draw fonts: */
static void draw_fonts(void)
{
  int i, off_y, max, font, most;
  SDL_Rect dest, src;
  SDL_Surface * tmp_surf;
  SDL_Color black = {0, 0, 0, 0};

  /* Draw the title: */
  draw_image_title(TITLE_LETTERS, r_ttoolopt);


  /* How many can we show? */

  most = 10;
  if (disable_stamp_controls)
    most = 14;

  /* Do we need scrollbars? */

  if (num_font_families > most + TOOLOFFSET)
    {
      off_y = 24;
      max = most-2 + TOOLOFFSET;

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40;

      if (font_scroll > 0)
	{
	  SDL_BlitSurface(img_scroll_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_up_off, NULL, screen, &dest);
	}

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + 24 + ((6 + TOOLOFFSET / 2) * 48);

      if (!disable_stamp_controls)
	dest.y = dest.y - (48 * 2);

      if (font_scroll < num_font_families - (most-2) - TOOLOFFSET)
	{
	  SDL_BlitSurface(img_scroll_down, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_down_off, NULL, screen, &dest);
	}
    }
  else
    {
      off_y = 0;
      max = most + TOOLOFFSET;
    }


  /* Draw each of the shown fonts: */

  for (font = font_scroll;
       font < font_scroll + max;
       font++)
    {
      i = font - font_scroll;


      dest.x = ((i % 2) * 48) + (WINDOW_WIDTH - 96);
      dest.y = ((i / 2) * 48) + 40 + off_y;

      if (font == cur_font)
	{
	  SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
	}
      else if (font < num_font_families)
	{
	  SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	}

    
      if (font < num_font_families)
	{
	  SDL_Surface * tmp_surf_1;
	  
	  tmp_surf_1 = render_text(getfonthandle(font),
                                            gettext("Aa"), black);

	  if (tmp_surf_1->w > 48 || tmp_surf_1->h > 48)
	  {
	    tmp_surf = thumbnail(tmp_surf_1, 48, 48, 1);
	    SDL_FreeSurface(tmp_surf_1);
	  }
	  else
	    tmp_surf = tmp_surf_1;

	  src.x = (tmp_surf->w - 48) / 2;
	  src.y = (tmp_surf->h - 48) / 2;
	  src.w = 48;
	  src.h = 48;
      
	  if (src.x < 0)
	    src.x = 0;
	  if (src.y < 0)
	    src.y = 0;

	  dest.x = ((i % 2) * 48) + (WINDOW_WIDTH - 96);
	  if (src.w > tmp_surf->w)
	    {
	      src.w = tmp_surf->w;
	      dest.x = dest.x + ((48 - (tmp_surf->w)) / 2);
	    }

      
	  dest.y = ((i / 2) * 48) + 40 + off_y;
	  if (src.h > tmp_surf->h)
	    {
	      src.h = tmp_surf->h;
	      dest.y = dest.y + ((48 - (tmp_surf->h)) / 2);
	    }
      
	  SDL_BlitSurface(tmp_surf, &src, screen, &dest);
      
	  SDL_FreeSurface(tmp_surf);
	}
    }


  /* Draw text controls: */

  if (!disable_stamp_controls)
    {
      SDL_Surface *button_color;
      SDL_Surface *button_body;

      /* Show bold button: */

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + ((5 + TOOLOFFSET / 2) * 48);

      if (text_state & TTF_STYLE_BOLD)
        SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
      else
        SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
    
      dest.x = WINDOW_WIDTH - 96 + (48 - img_bold->w) / 2;
      dest.y = (40 + ((5 + TOOLOFFSET / 2) * 48) +
		(48 - img_bold->h) / 2);

      SDL_BlitSurface(img_bold, NULL, screen, &dest);

    
      /* Show italic button: */

      dest.x = WINDOW_WIDTH - 48;
      dest.y = 40 + ((5 + TOOLOFFSET / 2) * 48);

      if (text_state & TTF_STYLE_ITALIC)
        SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
      else
        SDL_BlitSurface(img_btn_up, NULL, screen, &dest);

      dest.x = WINDOW_WIDTH - 48 + (48 - img_italic->w) / 2;
      dest.y = (40 + ((5 + TOOLOFFSET / 2) * 48) +
		(48 - img_italic->h) / 2);
    
      SDL_BlitSurface(img_italic, NULL, screen, &dest);

    
      /* Show shrink button: */

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + ((6 + TOOLOFFSET / 2) * 48);

      if (text_size > MIN_TEXT_SIZE)
        {
          button_color = img_black;
          button_body  = img_btn_up;
	}
      else
        {
          button_color = img_grey;
          button_body  = img_btn_off;
        }
      SDL_BlitSurface(button_body, NULL, screen, &dest);
    
      dest.x = WINDOW_WIDTH - 96 + (48 - img_shrink->w) / 2;
      dest.y = (40 + ((6 + TOOLOFFSET / 2) * 48) +
		(48 - img_shrink->h) / 2);

      SDL_BlitSurface(button_color, NULL, img_shrink, NULL);
      SDL_BlitSurface(img_shrink, NULL, screen, &dest);


      /* Show grow button: */

      dest.x = WINDOW_WIDTH - 48;
      dest.y = 40 + ((6 + TOOLOFFSET / 2) * 48);

      if (text_size < MAX_TEXT_SIZE)
        {
          button_color = img_black;
          button_body  = img_btn_up;
	}
      else
        {
          button_color = img_grey;
          button_body  = img_btn_off;
        }
      SDL_BlitSurface(button_body, NULL, screen, &dest);

      dest.x = WINDOW_WIDTH - 48 + (48 - img_grow->w) / 2;
      dest.y = (40 + ((6 + TOOLOFFSET / 2) * 48) +
		(48 - img_grow->h) / 2);
    
      SDL_BlitSurface(button_color, NULL, img_grow, NULL);
      SDL_BlitSurface(img_grow, NULL, screen, &dest);
    }
}


/* Draw stamps: */

static void draw_stamps(void)
{
  int i, off_y, max, stamp, most;
  int base_x, base_y;
  SDL_Rect dest;
  SDL_Surface * img;
  int sizes, size_at;
  float x_per, y_per;
  int xx, yy;
  SDL_Surface * btn, * blnk;


  /* Draw the title: */
  draw_image_title(TITLE_STAMPS, r_ttoolopt);


  /* How many can we show? */

  most = 10;
  if (disable_stamp_controls)
    most = 14;


  /* Do we need scrollbars? */

  if (num_stamps > most + TOOLOFFSET)
    {
      off_y = 24;
      max = (most - 2) + TOOLOFFSET;

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40;

      if (stamp_scroll > 0)
	{
	  SDL_BlitSurface(img_scroll_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_up_off, NULL, screen, &dest);
	}

    
      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + 24 + ((6 + TOOLOFFSET / 2) * 48);

      if (!disable_stamp_controls)
	dest.y = dest.y - (48 * 2);

      if (stamp_scroll < num_stamps - (most - 2) - TOOLOFFSET)
	{
	  SDL_BlitSurface(img_scroll_down, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_scroll_down_off, NULL, screen, &dest);
	}
    }
  else
    {
      off_y = 0;
      max = most + TOOLOFFSET;
    }


  /* Draw each of the shown stamps: */

  for (stamp = stamp_scroll;
       stamp < stamp_scroll + max;
       stamp++)
    {
      i = stamp - stamp_scroll;


      dest.x = ((i % 2) * 48) + (WINDOW_WIDTH - 96);
      dest.y = ((i / 2) * 48) + 40 + off_y;

      if (stamp == cur_stamp)
	{
	  SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
	}
      else if (stamp < num_stamps)
	{
	  SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	}

      if (stamp < num_stamps)
	{
          get_stamp_thumb(stamp_data[stamp]);
          img = stamp_data[stamp]->thumbnail;

          base_x = ((i % 2) * 48) + (WINDOW_WIDTH - 96) +
            ((48 - (img->w)) / 2);

          base_y = ((i / 2) * 48) + 40 + ((48 - (img->h)) / 2) + off_y;

          dest.x = base_x;
          dest.y = base_y;

          SDL_BlitSurface(img, NULL, screen, &dest);
        }
    }


  /* Draw stamp controls: */

  if (!disable_stamp_controls)
    {
      SDL_Surface *button_color;
      SDL_Surface *button_body;

      /* Show mirror button: */

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + ((5 + TOOLOFFSET / 2) * 48);

      if (stamp_data[cur_stamp]->mirrorable)
	{
	  if (stamp_data[cur_stamp]->mirrored)
	    {
	      button_color = img_black;
	      button_body  = img_btn_down;
	    }
	  else
	    {
	      button_color = img_black;
	      button_body  = img_btn_up;
	    }
	}
      else
	{
	  button_color = img_grey;
	  button_body  = img_btn_off;
	}
      SDL_BlitSurface(button_body, NULL, screen, &dest);
    
      dest.x = WINDOW_WIDTH - 96 + (48 - img_magics[MAGIC_MIRROR]->w) / 2;
      dest.y = (40 + ((5 + TOOLOFFSET / 2) * 48) +
		(48 - img_magics[MAGIC_MIRROR]->h) / 2);

      SDL_BlitSurface(button_color, NULL, img_magics[MAGIC_MIRROR], NULL);
      SDL_BlitSurface(img_magics[MAGIC_MIRROR], NULL, screen, &dest);

    
      /* Show flip button: */

      dest.x = WINDOW_WIDTH - 48;
      dest.y = 40 + ((5 + TOOLOFFSET / 2) * 48);

      if (stamp_data[cur_stamp]->flipable)
	{
	  if (stamp_data[cur_stamp]->flipped)
	    {
	      button_color = img_black;
	      button_body  = img_btn_down;
	    }
	  else
	    {
	      button_color = img_black;
	      button_body  = img_btn_up;
	    }
	}
      else
	{
	  button_color = img_grey;
	  button_body  = img_btn_off;
	}
      SDL_BlitSurface(button_body, NULL, screen, &dest);

      dest.x = WINDOW_WIDTH - 48 + (48 - img_magics[MAGIC_FLIP]->w) / 2;
      dest.y = (40 + ((5 + TOOLOFFSET / 2) * 48) +
		(48 - img_magics[MAGIC_FLIP]->h) / 2);

      SDL_BlitSurface(button_color, NULL, img_magics[MAGIC_FLIP], NULL);
      SDL_BlitSurface(img_magics[MAGIC_FLIP], NULL, screen, &dest);

   
#ifdef OLD_STAMP_GROW_SHRINK
      /* Show shrink button: */

      dest.x = WINDOW_WIDTH - 96;
      dest.y = 40 + ((6 + TOOLOFFSET / 2) * 48);

      if (stamp_data[cur_stamp]->size > MIN_STAMP_SIZE)
        {
          button_color = img_black;
          button_body  = img_btn_up;
	}
      else
        {
          button_color = img_grey;
          button_body  = img_btn_off;
        }
      SDL_BlitSurface(button_body, NULL, screen, &dest);
    
      dest.x = WINDOW_WIDTH - 96 + (48 - img_shrink->w) / 2;
      dest.y = (40 + ((6 + TOOLOFFSET / 2) * 48) +
		(48 - img_shrink->h) / 2);

      SDL_BlitSurface(button_color, NULL, img_shrink, NULL);
      SDL_BlitSurface(img_shrink, NULL, screen, &dest);

    
      /* Show grow button: */

      dest.x = WINDOW_WIDTH - 48;
      dest.y = 40 + ((6 + TOOLOFFSET / 2) * 48);

      if (stamp_data[cur_stamp]->size < MAX_STAMP_SIZE)
        {
          button_color = img_black;
          button_body  = img_btn_up;
	}
      else
        {
          button_color = img_grey;
          button_body  = img_btn_off;
        }
      SDL_BlitSurface(button_body, NULL, screen, &dest);
    
      dest.x = WINDOW_WIDTH - 48 + (48 - img_grow->w) / 2;
      dest.y = (40 + ((6 + TOOLOFFSET / 2) * 48) +
		(48 - img_grow->h) / 2);

      SDL_BlitSurface(button_color, NULL, img_grow, NULL);
      SDL_BlitSurface(img_grow, NULL, screen, &dest);

#else
  sizes = MAX_STAMP_SIZE - MIN_STAMP_SIZE;
  size_at = (stamp_data[cur_stamp]->size - MIN_STAMP_SIZE);
  x_per = 96.0 / sizes;
  y_per = 48.0 / sizes;
  
  for (i = 0; i < sizes; i++)
  {
    xx = ceil(x_per);
    yy = ceil(y_per * i);

    if (i <= size_at)
      btn = thumbnail(img_btn_down, xx, yy, 0);
    else
      btn = thumbnail(img_btn_up, xx, yy, 0);

    blnk = thumbnail(img_btn_off, xx, 48 - yy, 0);

    /* FIXME: Check for NULL! */

    dest.x = (WINDOW_WIDTH - 96) + (i * x_per);
    dest.y = (((7 + TOOLOFFSET / 2) * 48)) - 8;
    SDL_BlitSurface(blnk, NULL, screen, &dest);

    dest.x = (WINDOW_WIDTH - 96) + (i * x_per);
    dest.y = (((8 + TOOLOFFSET / 2) * 48)) - 8 - (y_per * i);
    SDL_BlitSurface(btn, NULL, screen, &dest);

    SDL_FreeSurface(btn);
    SDL_FreeSurface(blnk);
  }
#endif
    }
}


/* Draw the shape selector: */

static void draw_shapes(void)
{
  int i;
  SDL_Rect dest;


  draw_image_title(TITLE_SHAPES, r_ttoolopt);

  for (i = 0; i < 14 + TOOLOFFSET; i++)
    {
      dest.x = ((i % 2) * 48) + WINDOW_WIDTH - 96;
      dest.y = ((i / 2) * 48) + 40;


      if (i == cur_shape)
	{
	  SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
	}
      else if (i < NUM_SHAPES)
	{
	  SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	}


      if (i < NUM_SHAPES)
	{
	  dest.x = ((i % 2) * 48) + 4 + WINDOW_WIDTH - 96;
	  dest.y = ((i / 2) * 48) + 40 + 4;
	
	  SDL_BlitSurface(img_shapes[i], NULL, screen, &dest);

	  dest.x = ((i % 2) * 48) + 4 + WINDOW_WIDTH - 96 +
	    (40 - img_shape_names[i]->w) / 2;
          dest.y = ((i / 2) * 48) + 40 + 4 + (44 - img_shape_names[i]->h);

          SDL_BlitSurface(img_shape_names[i], NULL, screen, &dest);
	}
    }
}


/* Draw the eraser selector: */

static void draw_erasers(void)
{
  int i, x, y, sz;
  SDL_Rect dest;


  draw_image_title(TITLE_ERASERS, r_ttoolopt);

  for (i = 0; i < 14 + TOOLOFFSET; i++)
    {
      dest.x = ((i % 2) * 48) + WINDOW_WIDTH - 96;
      dest.y = ((i / 2) * 48) + 40;


      if (i == cur_eraser)
	{
	  SDL_BlitSurface(img_btn_down, NULL, screen, &dest);
	}
      else if (i < NUM_ERASERS)
	{
	  SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
	}
      else
	{
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	}


      if (i < NUM_ERASERS)
	{
	  sz = (2 + ((NUM_ERASERS - 1 - i) * (38 / (NUM_ERASERS - 1))));

	  x = ((i % 2) * 48) + WINDOW_WIDTH - 96 + 24 - sz / 2;
	  y = ((i / 2) * 48) + 40 + 24 - sz / 2;

	  dest.x = x;
	  dest.y = y;
	  dest.w = sz;
	  dest.h = 2;

	  SDL_FillRect(screen, &dest,
		       SDL_MapRGB(screen->format, 0, 0, 0));

	  dest.x = x;
	  dest.y = y + sz - 2;
	  dest.w = sz;
	  dest.h = 2;

	  SDL_FillRect(screen, &dest,
		       SDL_MapRGB(screen->format, 0, 0, 0));

	  dest.x = x;
	  dest.y = y;
	  dest.w = 2;
	  dest.h = sz;

	  SDL_FillRect(screen, &dest,
		       SDL_MapRGB(screen->format, 0, 0, 0));
	  
	  dest.x = x + sz - 2;
	  dest.y = y;
	  dest.w = 2;
	  dest.h = sz;

	  SDL_FillRect(screen, &dest,
		       SDL_MapRGB(screen->format, 0, 0, 0));
	}
    }
}


/* Draw no selectables: */

static void draw_none(void)
{
  int i;
  SDL_Rect dest;

  dest.x = WINDOW_WIDTH - 96;
  dest.y = 0;
  SDL_BlitSurface(img_title_off, NULL, screen, &dest);

  for (i = 0; i < 14 + TOOLOFFSET; i++)
    {
      dest.x = ((i % 2) * 48) + WINDOW_WIDTH - 96;
      dest.y = ((i / 2) * 48) + 40;

      SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
    }
}



/* Create a thumbnail: */

static SDL_Surface * thumbnail(SDL_Surface * src, int max_x, int max_y,
			int keep_aspect)
{
  int x, y;
  float src_x, src_y, off_x, off_y;
  SDL_Surface * s;
  Uint32 amask, tr, tg, tb, ta;
  Uint8 r, g, b, a;
  float xscale, yscale;
  int tmp;
  void (*putpixel)(SDL_Surface *, int, int, Uint32);
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[src->format->BytesPerPixel];


  /* Determine scale and centering offsets: */

  if (!keep_aspect)
    {
      yscale = (float) ((float) src->h / (float) max_y);
      xscale = (float) ((float) src->w / (float) max_x);

      off_x = 0;
      off_y = 0;
    }
  else
    {
      if (src->h > src->w)
	{
	  yscale = (float) ((float) src->h / (float) max_y);
	  xscale = yscale;

	  off_x = ((src->h - src->w) / xscale) / 2;
	  off_y = 0;
	}
      else
	{
	  xscale = (float) ((float) src->w / (float) max_x);
	  yscale = xscale;

	  off_x = 0;
	  off_y = ((src->w - src->h) / xscale) / 2;
	}
    }


  /* Create surface for thumbnail: */

  amask = ~(src->format->Rmask |
            src->format->Gmask |
            src->format->Bmask);
  
  s = SDL_CreateRGBSurface(src->flags, /* SDL_SWSURFACE, */
		           max_x, max_y,
			   src->format->BitsPerPixel,
			   src->format->Rmask,
			   src->format->Gmask,
			   src->format->Bmask,
			   src->format->Amask); /* amask); */


  if (s == NULL)
    {
      fprintf(stderr, "\nError: Can't build stamp thumbnails\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }

  putpixel= putpixels[s->format->BytesPerPixel];

  /* Create thumbnail itself: */

  SDL_LockSurface(src);
  SDL_LockSurface(s);

  for (y = 0; y < max_y; y++)
    {
      for (x = 0; x < max_x; x++)
	{
#ifndef LOW_QUALITY_THUMBNAILS
	  tr = 0;
	  tg = 0;
	  tb = 0;
	  ta = 0;

	  tmp = 0;

	  for (src_y = y * yscale; src_y < y * yscale + yscale &&
		 src_y < src->h; src_y++)
	    {
	      for (src_x = x * xscale; src_x < x * xscale + xscale &&
		     src_x < src->w; src_x++)
		{
		  SDL_GetRGBA(getpixel(src, src_x, src_y),
			      src->format,
			      &r, &g, &b, &a);

		  tr = tr + r;
		  tb = tb + b;
		  tg = tg + g;
		  ta = ta + a;

		  tmp++;
		}
	    }

	  if (tmp != 0)
	    {
	      tr = tr / tmp;
	      tb = tb / tmp;
	      tg = tg / tmp;
	      ta = ta / tmp;

	      putpixel(s, x + off_x, y + off_y, SDL_MapRGBA(s->format,
							    (Uint8) tr,
							    (Uint8) tg,
							    (Uint8) tb,
							    (Uint8) ta));
	    }
#else
	  src_x = x * xscale;
	  src_y = y * yscale;

	  putpixel(s, x + off_x, y + off_y, getpixel(src, src_x, src_y));
#endif
	}
    }

  SDL_UnlockSurface(s);
  SDL_UnlockSurface(src);

  return s;
}


/* Get a pixel: */
static Uint32 getpixel8(SDL_Surface * surface, int x, int y)
{
  Uint8 * p;

  /* get the X/Y values within the bounds of this surface */
  if (unlikely( (unsigned)x > (unsigned)surface->w - 1u ))
    x = (x<0) ? 0 : surface->w - 1;
  if (unlikely( (unsigned)y > (unsigned)surface->h - 1u ))
    y = (y<0) ? 0 : surface->h - 1;

  /* Set a pointer to the exact location in memory of the pixel
     in question: */

  p = (Uint8 *) (((Uint8 *)surface->pixels) +  /* Start at top of RAM */
    (y * surface->pitch) +  /* Go down Y lines */
    x);             /* Go in X pixels */


  /* Return the correctly-sized piece of data containing the
   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
   * RGB value) */

  return(*p);
}


/* Get a pixel: */
static Uint32 getpixel16(SDL_Surface * surface, int x, int y)
{
  Uint8 * p;

  /* get the X/Y values within the bounds of this surface */
  if (unlikely( (unsigned)x > (unsigned)surface->w - 1u ))
    x = (x<0) ? 0 : surface->w - 1;
  if (unlikely( (unsigned)y > (unsigned)surface->h - 1u ))
    y = (y<0) ? 0 : surface->h - 1;

  /* Set a pointer to the exact location in memory of the pixel
     in question: */

  p = (Uint8 *) (((Uint8 *)surface->pixels) +  /* Start at top of RAM */
    (y * surface->pitch) +  /* Go down Y lines */
    (x * 2));             /* Go in X pixels */


  /* Return the correctly-sized piece of data containing the
   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
   * RGB value) */

  return(*(Uint16 *)p);
}

/* Get a pixel: */
static Uint32 getpixel24(SDL_Surface * surface, int x, int y)
{
  Uint8 * p;
  Uint32 pixel;

  /* get the X/Y values within the bounds of this surface */
  if (unlikely( (unsigned)x > (unsigned)surface->w - 1u ))
    x = (x<0) ? 0 : surface->w - 1;
  if (unlikely( (unsigned)y > (unsigned)surface->h - 1u ))
    y = (y<0) ? 0 : surface->h - 1;

  /* Set a pointer to the exact location in memory of the pixel
     in question: */

  p = (Uint8 *) (((Uint8 *)surface->pixels) +  /* Start at top of RAM */
    (y * surface->pitch) +  /* Go down Y lines */
    (x * 3));             /* Go in X pixels */


  /* Return the correctly-sized piece of data containing the
   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
   * RGB value) */

  /* Depending on the byte-order, it could be stored RGB or BGR! */

  if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
    pixel = p[0] << 16 | p[1] << 8 | p[2];
  else
    pixel = p[0] | p[1] << 8 | p[2] << 16;

  return pixel;
}

/* Get a pixel: */
static Uint32 getpixel32(SDL_Surface * surface, int x, int y)
{
  Uint8 * p;

  /* get the X/Y values within the bounds of this surface */
  if (unlikely( (unsigned)x > (unsigned)surface->w - 1u ))
    x = (x<0) ? 0 : surface->w - 1;
  if (unlikely( (unsigned)y > (unsigned)surface->h - 1u ))
    y = (y<0) ? 0 : surface->h - 1;

  /* Set a pointer to the exact location in memory of the pixel
     in question: */

  p = (Uint8 *) (((Uint8 *)surface->pixels) +  /* Start at top of RAM */
    (y * surface->pitch) +  /* Go down Y lines */
    (x * 4));             /* Go in X pixels */


  /* Return the correctly-sized piece of data containing the
   * pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
   * RGB value) */

  return *(Uint32 *)p;  // 32-bit display
}


/* Draw a single pixel into the surface: */
static void putpixel8(SDL_Surface * surface, int x, int y, Uint32 pixel)
{
  Uint8 * p;

  /* Assuming the X/Y values are within the bounds of this surface... */
  if (likely( likely((unsigned)x<(unsigned)surface->w) && likely((unsigned)y<(unsigned)surface->h) ))
    {
      // Set a pointer to the exact location in memory of the pixel
      p = (Uint8 *) (((Uint8 *)surface->pixels) + /* Start: beginning of RAM */
		     (y * surface->pitch) +  /* Go down Y lines */
                     x);             /* Go in X pixels */


      /* Set the (correctly-sized) piece of data in the surface's RAM
       *          to the pixel value sent in: */

      *p = pixel;
    }
}

/* Draw a single pixel into the surface: */
static void putpixel16(SDL_Surface * surface, int x, int y, Uint32 pixel)
{
  Uint8 * p;

  /* Assuming the X/Y values are within the bounds of this surface... */
  if (likely( likely((unsigned)x<(unsigned)surface->w) && likely((unsigned)y<(unsigned)surface->h) ))
    {
      // Set a pointer to the exact location in memory of the pixel
      p = (Uint8 *) (((Uint8 *)surface->pixels) + /* Start: beginning of RAM */
		     (y * surface->pitch) +  /* Go down Y lines */
                     (x * 2));             /* Go in X pixels */


      /* Set the (correctly-sized) piece of data in the surface's RAM
       *          to the pixel value sent in: */

      *(Uint16 *)p = pixel;
    }
}

/* Draw a single pixel into the surface: */
static void putpixel24(SDL_Surface * surface, int x, int y, Uint32 pixel)
{
  Uint8 * p;

  /* Assuming the X/Y values are within the bounds of this surface... */
  if (likely( likely((unsigned)x<(unsigned)surface->w) && likely((unsigned)y<(unsigned)surface->h) ))
    {
      // Set a pointer to the exact location in memory of the pixel
      p = (Uint8 *) (((Uint8 *)surface->pixels) + /* Start: beginning of RAM */
		     (y * surface->pitch) +  /* Go down Y lines */
                     (x * 3));             /* Go in X pixels */


      /* Set the (correctly-sized) piece of data in the surface's RAM
       *          to the pixel value sent in: */

      if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
        {
          p[0] = (pixel >> 16) & 0xff;
          p[1] = (pixel >> 8) & 0xff;
          p[2] = pixel & 0xff;
        }
      else
        {
          p[0] = pixel & 0xff;
          p[1] = (pixel >> 8) & 0xff;
          p[2] = (pixel >> 16) & 0xff;
        }
      
    }
}

/* Draw a single pixel into the surface: */
static void putpixel32(SDL_Surface * surface, int x, int y, Uint32 pixel)
{
  Uint8 * p;

  /* Assuming the X/Y values are within the bounds of this surface... */
  if (likely( likely((unsigned)x<(unsigned)surface->w) && likely((unsigned)y<(unsigned)surface->h) ))
    {
      // Set a pointer to the exact location in memory of the pixel
      p = (Uint8 *) (((Uint8 *)surface->pixels) + /* Start: beginning of RAM */
		     (y * surface->pitch) +  /* Go down Y lines */
                     (x * 4));             /* Go in X pixels */


      /* Set the (correctly-sized) piece of data in the surface's RAM
       *          to the pixel value sent in: */

      *(Uint32 *)p = pixel;  // 32-bit display
    }
}


// XOR must show up on black, white, 0x7f grey, and 0x80 grey.
// XOR must be exactly 100% perfectly reversable.
static void xorpixel(int x, int y)
{
  Uint8 * p;
  int BytesPerPixel;

  // if outside the canvas, return
  if( (unsigned)x >= (unsigned)canvas->w || (unsigned)y >= (unsigned)canvas->h)
    return;
  // now switch to screen coordinates
  x += r_canvas.x;
  y += r_canvas.y;

  // Always 4, except 3 when loading a saved image.
  BytesPerPixel = screen->format->BytesPerPixel;

  // Set a pointer to the exact location in memory of the pixel
  p = (Uint8 *) (((Uint8 *)screen->pixels) + /* Start: beginning of RAM */
                 (y * screen->pitch) +  /* Go down Y lines */
                 (x * BytesPerPixel));             /* Go in X pixels */

  // XOR the (correctly-sized) piece of data in the screen's RAM
  if (likely(BytesPerPixel == 4))
    *(Uint32 *)p ^= 0x80808080u;  // 32-bit display
  else if (BytesPerPixel == 1)
    *p ^= 0x80;
  else if (BytesPerPixel == 2)
    *(Uint16 *)p ^= 0xd6d6;
  else if (BytesPerPixel == 3)
    {
      p[0] ^= 0x80;
      p[1] ^= 0x80;
      p[2] ^= 0x80;
    }
}


/* Undo! */

static void do_undo(void)
{
  int wanna_update_toolbar;

  wanna_update_toolbar = 0;

  if (cur_undo != oldest_undo)
    {
      cur_undo--;

      if (cur_undo < 0)
	cur_undo = NUM_UNDO_BUFS - 1;

#ifdef DEBUG
      printf("BLITTING: %d\n", cur_undo);
#endif
      SDL_BlitSurface(undo_bufs[cur_undo], NULL, canvas, NULL);


      if (img_starter != NULL)
      {
        if (undo_starters[cur_undo] == UNDO_STARTER_MIRRORED)
        {
	  starter_mirrored = !starter_mirrored;
	  mirror_starter();
        }
        else if (undo_starters[cur_undo] == UNDO_STARTER_FLIPPED)
        {
	  starter_flipped = !starter_flipped;
	  flip_starter();
        }
      }

      update_canvas(0, 0, (WINDOW_WIDTH - 96), (48 * 7) + 40 + HEIGHTOFFSET);


      if (cur_undo == oldest_undo)
	{
	  tool_avail[TOOL_UNDO] = 0;
	  wanna_update_toolbar = 1;
	}

      if (tool_avail[TOOL_REDO] == 0)
	{
	  tool_avail[TOOL_REDO] = 1;
	  wanna_update_toolbar = 1;
	}

      if (wanna_update_toolbar)
	{
	  draw_toolbar();
	  update_screen_rect(&r_tools);
	}

      been_saved = 0;
    }

#ifdef DEBUG
  printf("UNDO: Current=%d  Oldest=%d  Newest=%d\n",
	 cur_undo, oldest_undo, newest_undo);
#endif
}


/* Redo! */

static void do_redo(void)
{
  if (cur_undo != newest_undo)
    {
      if (img_starter != NULL)
      {
        if (undo_starters[cur_undo] == UNDO_STARTER_MIRRORED)
        {
	  starter_mirrored = !starter_mirrored;
	  mirror_starter();
        }
        else if (undo_starters[cur_undo] == UNDO_STARTER_FLIPPED)
        {
	  starter_flipped = !starter_flipped;
	  flip_starter();
        }
      }

      cur_undo = (cur_undo + 1) % NUM_UNDO_BUFS;

#ifdef DEBUG
      printf("BLITTING: %d\n", cur_undo);
#endif
      SDL_BlitSurface(undo_bufs[cur_undo], NULL, canvas, NULL);

      update_canvas(0, 0, (WINDOW_WIDTH - 96), (48 * 7) + 40 + HEIGHTOFFSET);

      been_saved = 0;
    }

#ifdef DEBUG
  printf("REDO: Current=%d  Oldest=%d  Newest=%d\n",
	 cur_undo, oldest_undo, newest_undo);
#endif


  if (((cur_undo + 1) % NUM_UNDO_BUFS) == newest_undo)
    {
      tool_avail[TOOL_REDO] = 0;
    }

  tool_avail[TOOL_UNDO] = 1;

  draw_toolbar();
  update_screen_rect(&r_tools);
}


/* Create the current brush in the current color: */

static void render_brush(void)
{
  Uint32 amask;
  int x, y;
  Uint8 r, g, b, a;
  Uint32 (*getpixel_brush)(SDL_Surface *, int, int) = getpixels[img_brushes[cur_brush]->format->BytesPerPixel];
  void (*putpixel_brush)(SDL_Surface *, int, int, Uint32) = putpixels[img_brushes[cur_brush]->format->BytesPerPixel];


  /* Kludge; not sure why cur_brush would become greater! */

  if (cur_brush >= num_brushes)
    cur_brush = 0;
  

  /* Free the old rendered brush (if any): */

  if (img_cur_brush != NULL)
    {
      SDL_FreeSurface(img_cur_brush);
    }


  /* Create a surface to render into: */

  amask = ~(img_brushes[cur_brush]->format->Rmask |
	    img_brushes[cur_brush]->format->Gmask |
	    img_brushes[cur_brush]->format->Bmask);

  img_cur_brush =
    SDL_CreateRGBSurface(SDL_SWSURFACE,
			 img_brushes[cur_brush]->w,
			 img_brushes[cur_brush]->h,
			 img_brushes[cur_brush]->format->BitsPerPixel,
			 img_brushes[cur_brush]->format->Rmask,
			 img_brushes[cur_brush]->format->Gmask,
			 img_brushes[cur_brush]->format->Bmask,
			 amask);

  if (img_cur_brush == NULL)
    {
      fprintf(stderr, "\nError: Can't render a brush!\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", SDL_GetError());

      cleanup();
      exit(1);
    }


  /* Render the new brush: */

  SDL_LockSurface(img_brushes[cur_brush]);
  SDL_LockSurface(img_cur_brush);

  for (y = 0; y < img_brushes[cur_brush]->h; y++)
    {
      for (x = 0; x < img_brushes[cur_brush]->w; x++)
	{
	  SDL_GetRGBA(getpixel_brush(img_brushes[cur_brush], x, y),
		      img_brushes[cur_brush]->format,
		      &r, &g, &b, &a);
	
	  putpixel_brush(img_cur_brush, x, y,
		   SDL_MapRGBA(img_cur_brush->format,
			       color_hexes[cur_color][0],
			       color_hexes[cur_color][1],
			       color_hexes[cur_color][2],
			       a));
	}
    }

  SDL_UnlockSurface(img_cur_brush);
  SDL_UnlockSurface(img_brushes[cur_brush]);

  brush_counter = 0;
}


/* Play a sound: */

static void playsound(int chan, int s, int override)
{
#ifndef NOSOUND
  if (!mute && use_sound && s != SND_NONE)
    {
      if (override || !Mix_Playing(chan))
	Mix_PlayChannel(chan, sounds[s], 0);
    }
#endif
}


/* Draw a XOR line: */

static void line_xor(int x1, int y1, int x2, int y2)
{
  int dx, dy, y, num_drawn;
  float m, b;


  /* Kludgey, but it works: */
  
  //SDL_LockSurface(screen);

  dx = x2 - x1;
  dy = y2 - y1;

  num_drawn = 0;

  if (dx != 0)
    {
      m = ((float) dy) / ((float) dx);
      b = y1 - m * x1;

      if (x2 >= x1)
	dx = 1;
      else
	dx = -1;


      while (x1 != x2)
	{
	  y1 = m * x1 + b;
	  y2 = m * (x1 + dx) + b;

	  if (y1 > y2)
	    {
	      y = y1;
	      y1 = y2;
	      y2 = y;
	    }
	
	  for (y = y1; y <= y2; y++)
	    {
	      num_drawn++;
	      if (num_drawn < 10 || dont_do_xor == 0)
	        xorpixel(x1,y);
	    }
	
	  x1 = x1 + dx;
	}
    }
  else
    {
      if (y1 > y2)
	{
          for (y = y1; y >= y2; y--)
	    {
	      num_drawn++;
	  
	      if (num_drawn < 10 || dont_do_xor == 0)
	        xorpixel(x1,y);
	    }
	}
      else
        {
          for (y = y1; y <= y2; y++)
	    {
	      num_drawn++;
	  
	      if (num_drawn < 10 || dont_do_xor == 0)
	        xorpixel(x1,y);
	    }
        }
    }

  //SDL_UnlockSurface(screen);
}


/* Draw a XOR rectangle: */

static void rect_xor(int x1, int y1, int x2, int y2)
{
  if (x1 < 0)
    x1 = 0;

  if (x2 < 0)
    x2 = 0;

  if (y1 < 0)
    y1 = 0;

  if (y2 < 0)
    y2 = 0;

  if (x1 >= (WINDOW_WIDTH - 96 - 96))
    x1 = (WINDOW_WIDTH - 96 - 96) - 1;

  if (x2 >= (WINDOW_WIDTH - 96 - 96))
    x2 = (WINDOW_WIDTH - 96 - 96) - 1;

  if (y1 >= (48 * 7) + 40 + HEIGHTOFFSET)
    y1 = (48 * 7) + 40 + HEIGHTOFFSET - 1;

  if (y2 >= (48 * 7) + 40 + HEIGHTOFFSET)
    y2 = (48 * 7) + 40 + HEIGHTOFFSET - 1;

  line_xor(x1, y1, x2, y1);
  line_xor(x2, y1, x2, y2);
  line_xor(x2, y2, x1, y2);
  line_xor(x1, y2, x1, y1);
}


/* Erase at the cursor! */

static void do_eraser(int x, int y)
{
  SDL_Rect dest;
  int sz;

  sz = (ERASER_MIN +
	((NUM_ERASERS - 1 - cur_eraser) *
	 ((ERASER_MAX - ERASER_MIN) / (NUM_ERASERS - 1))));

  dest.x = x - (sz / 2);
  dest.y = y - (sz / 2);
  dest.w = sz;
  dest.h = sz;

  if (img_starter_bkgd == NULL)
  {
    SDL_FillRect(canvas, &dest,
	         SDL_MapRGB(canvas->format, 255, 255, 255));
  }
  else
  {
    SDL_BlitSurface(img_starter_bkgd, &dest, canvas, &dest);
  }


#ifndef NOSOUND
  if (!mute && use_sound)
    {
      if (!Mix_Playing(0))
	{
	  eraser_sound = (eraser_sound + 1) % 2;
	
	  playsound(0, SND_ERASER1 + eraser_sound, 0);
	}
    }
#endif

  update_canvas(x - sz / 2, y - sz / 2, x + sz / 2, y + sz / 2);

  rect_xor(x - sz / 2, y - sz / 2,
           x + sz / 2, y + sz / 2);
}


/* Reset available tools (for new image / starting out): */

static void reset_avail_tools(void)
{
  int i;
  int disallow_print = disable_print;  /* set to 1 later if printer unavail */

  for (i = 0; i < NUM_TOOLS; i++)
    {
      tool_avail[i] = 1;
    }


  /* Unavailable at the beginning of a new canvas: */

  tool_avail[TOOL_UNDO] = 0;
  tool_avail[TOOL_REDO] = 0;

  if (been_saved)
    tool_avail[TOOL_SAVE] = 0;


  /* Unavailable in rare circumstances: */

  if (num_stamps == 0)
    tool_avail[TOOL_STAMP] = 0;


  /* Disable quit? */

  if (disable_quit)
    tool_avail[TOOL_QUIT] = 0;


  /* Disable save? */

  if (disable_save)
    tool_avail[TOOL_SAVE] = 0;


#ifdef WIN32
  if(!IsPrinterAvailable()) disallow_print = 1;
#endif

#ifdef __BEOS__
  if(!IsPrinterAvailable()) disallow_print = disable_print = 1;
#endif


  /* Disable print? */

  if (disallow_print)
    tool_avail[TOOL_PRINT] = 0;
}


/* Save and disable available tools (for Open-Dialog) */

static void disable_avail_tools(void)
{
  int i;

  for (i = 0; i < NUM_TOOLS; i++)
    {
      tool_avail_bak[i] = tool_avail[i];
      tool_avail[i]=0;
    }
}

/* Restore and enable available tools (for End-Of-Open-Dialog) */

static void enable_avail_tools(void)
{
  int i;

  for (i = 0; i < NUM_TOOLS; i++)
    {
      tool_avail[i] = tool_avail_bak[i];
    }
}


/* For qsort() call in do_open()... */

static int compare_dirent2s(struct dirent2 * f1, struct dirent2 * f2)
{
#ifdef DEBUG
  printf("compare_dirents: %s\t%s\n", f1->f.d_name, f2->f.d_name);
#endif

  if (f1->place == f2->place)
    return (strcmp(f1->f.d_name, f2->f.d_name));
  else
    return (f1->place - f2->place);
}


/* Draw tux's text on the screen: */

static void draw_tux_text(int which_tux, const char * const str,
	 	          int want_right_to_left)
{
  SDL_Rect dest;
  SDL_Color black = {0, 0, 0, 0};

  /* Remove any text-changing timer if one is running: */
  control_drawtext_timer(0, "");

  /* Clear first: */
  SDL_FillRect(screen, &r_tuxarea, SDL_MapRGB(screen->format, 255, 255, 255));

  /* Draw tux: */
  dest.x = r_tuxarea.x;
  dest.y = r_tuxarea.y + r_tuxarea.h - img_tux[which_tux]->h;

  // if he's too tall to fit, go off the bottom (not top) edge
  if (dest.y < r_tuxarea.y)
    dest.y = r_tuxarea.y;

  SDL_BlitSurface(img_tux[which_tux], NULL, screen, &dest);

  wordwrap_text(str, black,
	        img_tux[which_tux]->w + 5,
	        r_tuxarea.y,
	        r_tuxarea.w,
		want_right_to_left);

  update_screen_rect(&r_tuxarea);
}


static void wordwrap_text(const char * const str, SDL_Color color,
		   int left, int top, int right,
		   int want_right_to_left)
{
  int x, y, j;
  unsigned int i;
  char substr[512];
  unsigned char * locale_str;
  char * tstr;
  unsigned char utf8_char[5];
  int len;
  SDL_Surface * text;
  SDL_Rect dest, src;

  int utf8_str_len, last_text_height;
  unsigned char utf8_str[512];


  /* Cursor starting position: */
  
  x = left;
  y = top;
      
  last_text_height = 0;

  debug(str);
  debug(gettext(str));
  debug("...");

  if (strcmp(str, "") != 0)
    {
      if (strcmp(str, gettext(str)) == 0)
      {
	/* String isn't translated!  Don't write right-to-left */
	want_right_to_left = 0;
      }

      if (want_right_to_left == 0)
	locale_str = (unsigned char *) strdup(gettext(str));
      else
	locale_str = (unsigned char *) textdir(gettext(str));


      /* For each UTF8 character: */

      utf8_str_len = 0;
      utf8_str[0] = '\0';

      for (i = 0; i <= strlen((char *) locale_str); i++)
	{
	  if (locale_str[i] < 128)
	    {
	      utf8_str[utf8_str_len++] = locale_str[i];
	      utf8_str[utf8_str_len] = '\0';


	      /* Space?  Blit the word! (Word-wrap if necessary) */

	      if (locale_str[i] == ' ' || locale_str[i] == '\0')
		{
		  text = render_text(locale_font, (char *) utf8_str, color);

		  if (!text) continue;  /* Didn't render anything... */

		  /* ----------- */
		  if (text->w > right - left)
		    {
		      /* Move left and down (if not already at left!) */

		      if (x > left)
			{
			  if (need_right_to_left && want_right_to_left)
			    anti_carriage_return(left, right, top, top + text->h, y + text->h,
						 x - left);

			  x = left;
			  y = y + text->h;
			}


		      /* Junk the blitted word; it's too long! */

		      last_text_height = text->h;
		      SDL_FreeSurface(text);

	
		      /* For each UTF8 character: */

		      for (j = 0; j < utf8_str_len; j++)
			{
			  /* How many bytes does this character need? */

			  if (utf8_str[j] < 128)  /* 0xxx xxxx - 1 byte */
			    {
			      utf8_char[0] = utf8_str[j];
			      utf8_char[1] = '\0';
			    }
			  else if ((utf8_str[j] & 0xE0) == 0xC0)  /* 110x xxxx - 2 bytes */
			    {
			      utf8_char[0] = utf8_str[j];
			      utf8_char[1] = utf8_str[j + 1];
			      utf8_char[2] = '\0';
			      j = j + 1;
			    }
			  else if ((utf8_str[j] & 0xF0) == 0xE0)  /* 1110 xxxx - 3 bytes */
			    {
			      utf8_char[0] = utf8_str[j];
			      utf8_char[1] = utf8_str[j + 1];
			      utf8_char[2] = utf8_str[j + 2];
			      utf8_char[3] = '\0';
			      j = j + 2;
			    }
			  else  /* 1111 0xxx - 4 bytes */
			    {
			      utf8_char[0] = utf8_str[j];
			      utf8_char[1] = utf8_str[j + 1];
			      utf8_char[2] = utf8_str[j + 2];
			      utf8_char[3] = utf8_str[j + 3];
			      utf8_char[4] = '\0';
			      j = j + 3;
			    }
    

			  if (utf8_char[0] != '\0')
			    {
			      text = render_text(locale_font, (char *) utf8_char, color);
			      if (text != NULL)
				{
				  if (x + text->w > right)
				    {
				      if (need_right_to_left && want_right_to_left)
					anti_carriage_return(left, right, top, top + text->h,
							     y + text->h, x - left);
	        
				      x = left;
				      y = y + text->h;
				    }

				  dest.x = x;

				  if (need_right_to_left && want_right_to_left)
				    dest.y = top;
				  else
				    dest.y = y;

				  SDL_BlitSurface(text, NULL, screen, &dest);
      
				  last_text_height = text->h;

				  x = x + text->w;

				  SDL_FreeSurface(text);
				}
			    }
			}
		    }
		  else
		    {
		      /* Not too wide for one line... */
	      
		      if (x + text->w > right)
			{
			  /* This word needs to move down? */

			  if (need_right_to_left && want_right_to_left)
			    anti_carriage_return(left, right, top, top + text->h, y + text->h,
						 x - left);
          
			  x = left;
			  y = y + text->h;
			}

		      dest.x = x;

		      if (need_right_to_left && want_right_to_left)
			dest.y = top;
		      else
			dest.y = y;

		      SDL_BlitSurface(text, NULL, screen, &dest);

		      last_text_height = text->h;
		      x = x + text->w;

		      SDL_FreeSurface(text);
		    }
        
      
		  utf8_str_len = 0;
		  utf8_str[0] = '\0';
		}
	    }
	  else if ((locale_str[i] & 0xE0) == 0xC0)
	    {
	      utf8_str[utf8_str_len++] = locale_str[i];
	      utf8_str[utf8_str_len++] = locale_str[i + 1];
	      utf8_str[utf8_str_len] = '\0';
	      i++;
	    }
	  else if ((locale_str[i] & 0xF0) == 0xE0)
	    {
	      utf8_str[utf8_str_len++] = locale_str[i];
	      utf8_str[utf8_str_len++] = locale_str[i + 1];
	      utf8_str[utf8_str_len++] = locale_str[i + 2];
	      utf8_str[utf8_str_len] = '\0';
	      i = i + 2;
	    }
	  else
	    {
	      utf8_str[utf8_str_len++] = locale_str[i];
	      utf8_str[utf8_str_len++] = locale_str[i + 1];
	      utf8_str[utf8_str_len++] = locale_str[i + 2];
	      utf8_str[utf8_str_len++] = locale_str[i + 3];
	      utf8_str[utf8_str_len] = '\0';
	      i = i + 3;
	    }
	}
    
      free(locale_str);
    }
  else if (strlen(str) != 0)
    {
      /* Truncate if too big! (sorry!) */

      {
        char *s1 = gettext(str);
        if (want_right_to_left)
          {
            char *freeme = s1;
            s1 = textdir(s1);
            free(freeme);
          }
	tstr = uppercase(s1);
	free(s1);
      }

      if (strlen(tstr) > sizeof(substr) - 1)
	tstr[sizeof(substr) - 1] = '\0';


      /* For each word... */

      for (i = 0; i < strlen(tstr); i++)
	{
	  /* Figure out the word... */

	  len = 0;

	  for (j = i; tstr[j] != ' ' && tstr[j] != '\0'; j++)
	    {
	      substr[len++] = tstr[j];
	    }

	  substr[len++] = ' ';
	  substr[len] = '\0';


	  /* Render the word for display... */

  
	  text = render_text(locale_font, substr, color);


	  /* If it won't fit on this line, move to the next! */

	  if (x + text->w > right)  /* Correct? */
	    {
	      if (need_right_to_left && want_right_to_left)
		anti_carriage_return(left, right, top, top + text->h, y + text->h,
				     x - left);
        
	      x = left;
	      y = y + text->h;
	    }


	  /* Draw the word: */

	  dest.x = x;

	  if (need_right_to_left && want_right_to_left)
	    dest.y = top;
	  else
	    dest.y = y;

	  SDL_BlitSurface(text, NULL, screen, &dest);


	  /* Move the cursor one word's worth: */

	  x = x + text->w;


	  /* Free the temp. surface: */

	  last_text_height = text->h;
	  SDL_FreeSurface(text);


	  /* Now on to the next word... */

	  i = j;
	}

      free(tstr);
    }


  /* Right-justify the final line of text, in right-to-left mode: */
  
  if (need_right_to_left && want_right_to_left && last_text_height > 0)
    {
      src.x = left;
      src.y = top;
      src.w = x - left;
      src.h = last_text_height;

      dest.x = right - src.w;
      dest.y = top;

      SDL_BlitSurface(screen, &src, screen, &dest);

      dest.x = left;
      dest.y = top;
      dest.w = (right - left - src.w);
      dest.h = last_text_height;

      SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 255, 255, 255));
    }
}


/* Load a file's sound: */

#ifndef NOSOUND

static Mix_Chunk * loadsound(const char * const fname)
{
  char * snd_fname;
  char tmp_str[64];
  Mix_Chunk * tmp_snd;


  debug(fname);


  /* First, check for localized version of sound: */

  snd_fname = malloc(strlen(fname) + strlen(lang_prefix) + 2);

  strcpy(snd_fname, fname);
  snprintf(tmp_str, sizeof(tmp_str), "_%s.wav", lang_prefix);


  if (strcasestr(snd_fname, ".png") != NULL)
    {
      strcpy(strcasestr(snd_fname, ".png"), tmp_str);
      debug(snd_fname);

      tmp_snd = Mix_LoadWAV(snd_fname);

      if (tmp_snd == NULL)
	{
	  debug("...No local version of sound!");

	  /* Now, check for default sound: */

	  free(snd_fname);

	  snd_fname = strdup(fname);

	  if (strcasestr(snd_fname, ".png") != NULL)
	    {
	      strcpy(strcasestr(snd_fname, ".png"), ".wav");
	      debug(snd_fname);
	      tmp_snd = Mix_LoadWAV(snd_fname);
	      free(snd_fname);

	      if (tmp_snd == NULL)
		{
		  debug("...No default version of sound!");
		  return NULL;
		}

	      return (tmp_snd);
	    }
	  else
	    {
	      return NULL;
	    }
	}

      return (tmp_snd);
    }
  else
    {
      return NULL;
    }
}

#endif


/* Strip any trailing spaces: */

static void strip_trailing_whitespace( char *buf )
{
  unsigned i = strlen(buf);
  while(i--)
    {
      if(!isspace(buf[i])) break;
      buf[i] = '\0';
    }
}


/* Load a file's description: */

static char * loaddesc(const char * const fname)
{
  char * txt_fname;
  char buf[256], def_buf[256];
  int found, got_first;
  FILE * fi;


  txt_fname = strdup(fname);

  if (strcasestr(txt_fname, ".png") != NULL)   // FIXME: isn't this always OK?
    {
      strcpy(strcasestr(txt_fname, ".png"), ".txt");

      fi = fopen(txt_fname, "r");
      free(txt_fname);

      if (!fi)
	return NULL;


      got_first = 0;
      found = 0;

      strcpy(def_buf, "");

      do
	{
	  fgets(buf, sizeof(buf), fi);

	  if (!feof(fi))
	    {
	      strip_trailing_whitespace(buf);


	      if (!got_first)
		{
		  /* First one is the default: */

		  strcpy(def_buf, buf);
		  got_first = 1;
		}


	      debug(buf);


	      /* See if it's the one for this locale... */
	
	      if (strcasestr(buf, lang_prefix) == buf)
		{

		  debug(buf + strlen(lang_prefix));
		  if (strcasestr(buf + strlen(lang_prefix), ".utf8=") ==
			   buf + strlen(lang_prefix))
		    {
		      found = 1;
		      
		      debug("...FOUND!");
		    }
		}
	    }
	}
      while (!feof(fi) && !found);

      fclose(fi);


      /* Return the string: */

      if (found)
	{
	  return(strdup(buf + (strlen(lang_prefix)) + 6));
	}
      else
	{
	  /* No locale-specific translation; use the default (English) */

	  return(strdup(def_buf));
	}
    }
  else
    {
      return NULL;
    }
}


/* Load a *.dat file */
static double loadinfo(const char * const fname, stamp_type *inf)
{
  char buf[256];
  FILE * fi;
  double ratio = 1.0;

  inf->colorable = 0;
  inf->tintable = 0;
  inf->mirrorable = 1;
  inf->flipable = 1;
  inf->tinter = TINTER_NORMAL;

  fi = fopen(fname, "r");
  if (!fi)
    return ratio;

  do
    {
      fgets(buf, sizeof buf, fi);

      if (!feof(fi))
        {
          strip_trailing_whitespace(buf);

          if (strcmp(buf, "colorable") == 0)
            inf->colorable = 1;
          else if (strcmp(buf, "tintable") == 0)
            inf->tintable = 1;
          else if (!memcmp(buf, "scale", 5) && (isspace(buf[5]) || buf[5]=='='))
            {
              double tmp, tmp2;
              char *cp = buf+6;
              while (isspace(*cp) || *cp=='=')
                cp++;
              if (strchr(cp,'%'))
                {
                  tmp = strtod(cp,NULL) / 100.0;
                  if (tmp > 0.0001 && tmp < 10000.0)
                    ratio = tmp;
                }
              else if (strchr(cp,'/'))
                {
                  tmp = strtod(cp,&cp);
                  while(*cp && !isdigit(*cp))
                    cp++;
                  tmp2 = strtod(cp,NULL);
                  if (tmp>0.0001 && tmp<10000.0 && tmp2>0.0001 && tmp2<10000.0 && tmp/tmp2>0.0001 && tmp/tmp2<10000.0)
                    ratio = tmp/tmp2;
                }
              else if (strchr(cp,':'))
                {
                  tmp = strtod(cp,&cp);
                  while(*cp && !isdigit(*cp))
                    cp++;
                  tmp2 = strtod(cp,NULL);
                  if (tmp>0.0001 && tmp<10000.0 && tmp2>0.0001 && tmp2<10000.0 && tmp2/tmp>0.0001 && tmp2/tmp<10000.0)
                    ratio = tmp2/tmp;
                }
              else
                {
                  tmp = strtod(cp,NULL);
                  if (tmp > 0.0001 && tmp < 10000.0)
                    ratio = 1.0 / tmp;
                }
            }
          else if (!memcmp(buf, "tinter", 6) && (isspace(buf[6]) || buf[6]=='='))
            {
              char *cp = buf+7;
              while (isspace(*cp) || *cp=='=')
                cp++;
              if (!strcmp(cp,"anyhue"))
                {
                    inf->tinter = TINTER_ANYHUE;
                }
              else if (!strcmp(cp,"narrow"))
                {
                    inf->tinter = TINTER_NARROW;
                }
              else if (!strcmp(cp,"normal"))
                {
                    inf->tinter = TINTER_NORMAL;
                }
              else if (!strcmp(cp,"vector"))
                {
                    inf->tinter = TINTER_VECTOR;
                }
              else
                {
                    debug(cp);
                }
            }
          else if (strcmp(buf, "nomirror") == 0)
            inf->mirrorable = 0;
          else if (strcmp(buf, "noflip") == 0)
            inf->flipable = 0;
        }
    }
  while (!feof(fi));

  fclose(fi);
  return ratio;
}


static int SDLCALL NondefectiveBlit(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect)
{
  int dstx = 0;
  int dsty = 0;
  int srcx = 0;
  int srcy = 0;
  int srcw = src->w;
  int srch = src->h;
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[src->format->BytesPerPixel];
  void (*putpixel)(SDL_Surface *, int, int, Uint32) = putpixels[dst->format->BytesPerPixel];

  
  if(srcrect)
    {
      srcx = srcrect->x;
      srcy = srcrect->y;
      srcw = srcrect->w;
      srch = srcrect->h;
    }
  if(dstrect)
    {
      dstx = dstrect->x;
      dsty = dstrect->y;
    }
  if(dsty<0)
    {
      srcy += -dsty;
      srch -= -dsty;
      dsty = 0;
    }
  if(dstx<0)
    {
      srcx += -dstx;
      srcw -= -dstx;
      dstx = 0;
    }
  if(dstx+srcw > dst->w-1)
    {
      srcw -= (dstx+srcw) - (dst->w-1);
    }
  if(dsty+srch > dst->h-1)
    {
      srch -= (dsty+srch) - (dst->h-1);
    }
  if(srcw<1 || srch<1)
    return -1; /* no idea what to return if nothing done */
  while(srch--)
    {
      int i = srcw;
      while(i--)
        {
          putpixel(dst, i+dstx, srch+dsty, getpixel(src, i+srcx, srch+srcy));
        }
    }

  return(0);
}


// For the 3rd arg, pass either NondefectiveBlit or SDL_BlitSurface.
static void autoscale_copy_smear_free(SDL_Surface *src, SDL_Surface *dst, int SDLCALL (*blit)(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect))
{
  SDL_Surface *src1;
  SDL_Rect dest;
  // What to do when in 640x480 mode, and loading an
  // 800x600 (or larger) image!? Scale it. Starters must
  // be scaled too. Keep the pixels square though, filling
  // in the gaps via a smear.
  if(src->w != dst->w || src->h != dst->h)
    {
      if(src->w / (float)dst->w  >  src->h / (float)dst->h)
        src1 = thumbnail(src, dst->w, src->h*dst->w/src->w, 0);
      else
        src1 = thumbnail(src, src->w*dst->h/src->h, dst->h, 0);
      SDL_FreeSurface(src);
      src = src1;
    }

  dest.x = (dst->w - src->w) / 2;
  dest.y = (dst->h - src->h) / 2;
  blit(src, NULL, dst, &dest);

  if(src->w != dst->w)
    {
      // we know that the heights match, and src is narrower
      SDL_Rect sour;
      int i = (dst->w - src->w) / 2;
      sour.w = 1;
      sour.x = 0;
      sour.h = src->h;
      sour.y = 0;
      while(i-- > 0)
        {
          dest.x = i;
          blit(src, &sour, dst, &dest);
        }
      sour.x = src->w - 1;
      i = (dst->w - src->w) / 2 + src->w - 1;
      while(++i < dst->w)
        {
          dest.x = i;
          blit(src, &sour, dst, &dest);
        }
    }

  if(src->h != dst->h)
    {
      // we know that the widths match, and src is shorter
      SDL_Rect sour;
      int i = (dst->h - src->h) / 2;
      sour.w = src->w;
      sour.x = 0;
      sour.h = 1;
      sour.y = 0;
      while(i-- > 0)
        {
          dest.y = i;
          blit(src, &sour, dst, &dest);
        }
      sour.y = src->h - 1;
      i = (dst->h - src->h) / 2 + src->h - 1;
      while(++i < dst->h)
        {
          dest.y = i;
          blit(src, &sour, dst, &dest);
        }
    }

  SDL_FreeSurface(src);
}


static void load_starter_id(char * saved_id)
{
  char * rname;
  char fname[32];
  FILE * fi;
  
  snprintf(fname, sizeof(fname), "saved/%s.dat", saved_id);
  rname = get_fname(fname);

  starter_id[0] = '\0';

  fi = fopen(rname, "r");
  if (fi != NULL)
  {
    fgets(starter_id, sizeof(starter_id), fi);
    starter_id[strlen(starter_id) - 1] = '\0';

    fscanf(fi, "%d", &starter_mirrored);
    fscanf(fi, "%d", &starter_flipped);

    fclose(fi);
  }

  free(rname);
}



static void load_starter(char * img_id)
{
  char * dirname;
  char fname[256];
  SDL_Surface * tmp_surf;

  /* Determine path to starter files: */
  
  dirname = strdup(DATA_PREFIX "starters");

  /* Clear them to NULL first: */
  img_starter = NULL;
  img_starter_bkgd = NULL;

  /* Load the core image: */
  snprintf(fname, sizeof(fname), "%s/%s.png", dirname, img_id);
  tmp_surf = IMG_Load(fname);

  if (tmp_surf != NULL)
  {
    img_starter = SDL_DisplayFormatAlpha(tmp_surf);
    SDL_FreeSurface(tmp_surf);
  }

  if (img_starter != NULL &&
      (img_starter->w != canvas->w || img_starter->h != canvas->h))
  {
    tmp_surf = img_starter;

    img_starter = SDL_CreateRGBSurface(canvas->flags,
					    canvas->w, canvas->h,
					    tmp_surf->format->BitsPerPixel,
			     	            tmp_surf->format->Rmask,
			      	            tmp_surf->format->Gmask,
			      	            tmp_surf->format->Bmask,
					    tmp_surf->format->Amask);

    // 3rd arg ignored for RGBA surfaces
    SDL_SetAlpha(tmp_surf, SDL_RLEACCEL, SDL_ALPHA_OPAQUE);
    autoscale_copy_smear_free(tmp_surf,img_starter,NondefectiveBlit);
    SDL_SetAlpha(img_starter, SDL_RLEACCEL|SDL_SRCALPHA, SDL_ALPHA_OPAQUE);
  }

  /* Try to load the a background image: */

  /* (JPEG first) */
  snprintf(fname, sizeof(fname), "%s/%s-back.jpeg", dirname, img_id);
  tmp_surf = IMG_Load(fname);
 
  /* (Failed? Try PNG next) */
  if (tmp_surf == NULL)
  {
    snprintf(fname, sizeof(fname), "%s/%s-back.png", dirname, img_id);
    tmp_surf = IMG_Load(fname);
  }
  
  if (tmp_surf != NULL)
  {
    img_starter_bkgd = SDL_DisplayFormat(tmp_surf);
    SDL_FreeSurface(tmp_surf);
  }

  if (img_starter_bkgd != NULL &&
      (img_starter_bkgd->w != canvas->w || img_starter_bkgd->h != canvas->h))
  {
    tmp_surf = img_starter_bkgd;

    img_starter_bkgd = SDL_CreateRGBSurface(SDL_SWSURFACE,
					    canvas->w, canvas->h,
					    canvas->format->BitsPerPixel,
			     	            canvas->format->Rmask,
			      	            canvas->format->Gmask,
			      	            canvas->format->Bmask,
					    0);

    autoscale_copy_smear_free(tmp_surf,img_starter_bkgd,SDL_BlitSurface);
  }

  free(dirname);
}


/* Load current (if any) image: */

static void load_current(void)
{
  SDL_Surface * tmp;
  char * fname;
  char ftmp[1024];
  FILE * fi;

  /* Determine the current picture's ID: */

  fname = get_fname("current_id.txt");

  fi = fopen(fname, "r");
  if (fi == NULL)
    {
      fprintf(stderr,
	      "\nWarning: Couldn't determine the current image's ID\n"
	      "%s\n"
	      "The system error that occurred was:\n"
	      "%s\n\n", fname, strerror(errno));
      file_id[0] = '\0';
      starter_id[0] = '\0';
    }
  else
    {
      fgets(file_id, sizeof(file_id), fi);
      if (strlen(file_id) > 0)
	{
	  file_id[strlen(file_id) - 1] = '\0';
	}
      fclose(fi);
    }

  free(fname);


  /* Load that image: */

  if (file_id[0] != '\0')
    {
      snprintf(ftmp, sizeof(ftmp), "saved/%s%s", file_id, FNAME_EXTENSION);

      fname = get_fname(ftmp);

#ifdef SAVE_AS_BMP
      tmp = SDL_LoadBMP(fname);
#else
      tmp = IMG_Load(fname);
#endif

      if (tmp == NULL)
	{
	  fprintf(stderr,
		  "\nWarning: Couldn't load any current image.\n"
		  "%s\n"
		  "The Simple DirectMedia Layer error that occurred was:\n"
		  "%s\n\n", fname, SDL_GetError());

	  file_id[0] = '\0';
	  starter_id[0] = '\0';
	}
      else
	{
	  autoscale_copy_smear_free(tmp,canvas,SDL_BlitSurface);
	  load_starter_id(file_id);
	  load_starter(starter_id);

          if (starter_mirrored)
            mirror_starter();

          if (starter_flipped)
            flip_starter();
	}

      free(fname);
    }
}


/* Save the current image to disk: */

static void save_current(void)
{
  char * fname;
  int res;
  FILE * fi;


  fname = get_fname("");

  res = mkdir(fname, 0755);

  if (res != 0 && errno != EEXIST)
    {
      fprintf(stderr,
	      "\nError: Can't create user data directory:\n"
	      "%s\n"
	      "The error that occurred was:\n"
	      "%s\n\n", fname, strerror(errno));

      draw_tux_text(TUX_OOPS, strerror(errno), 0);
    }

  free(fname);


  fname = get_fname("current_id.txt");

  fi = fopen(fname, "w");
  if (fi == NULL)
    {
      fprintf(stderr,
	      "\nError: Can't keep track of current image.\n"
	      "%s\n"
	      "The error that occurred was:\n"
	      "%s\n\n", fname, strerror(errno));

      draw_tux_text(TUX_OOPS, strerror(errno), 0);
    }
  else
    {
      fprintf(fi, "%s\n", file_id);
      fclose(fi);
    }

  free(fname);
}


/* The filename for the current image: */

static char * get_fname(const char * const name)
{
  char f[512];
  const char * tux_settings_dir;


  /* Where is the user's data directory?
     This is where their saved files are stored,
     local fonts, brushes and stamps can be found,
     and where the "current_id.txt" file is saved */

#ifdef WIN32
  /* Windows predefines "savedir" as:
     "C:\Documents and Settings\%USERNAME%\Application Data\TuxPaint"
     though it may get overridden with "--savedir" option */

  snprintf(f, sizeof(f), "%s/%s", savedir, name);

#elif __BEOS__
  /* BeOS similarly predefines "savedir" as "./userdata"... */
 
  if (*name == '\0')
    strcpy(f, savedir);
  else
    snprintf(f, sizeof(f), "%s/%s", savedir, name);

#else

#ifdef __APPLE__
  /* Macintosh: It's under ~/Library/Application Support/TuxPaint */
  
  tux_settings_dir = "Library/Application Support/TuxPaint";
#else
  /* Linux & Unix: It's under ~/.tuxpaint */
  
  tux_settings_dir = ".tuxpaint";
#endif
 

  /* Put together home directory path + settings directory + filename... */
  
  if (savedir == NULL)
    {
      /* Save directory not overridden: */
	 
      if (getenv("HOME") != NULL)
	{
	  if (*name == '\0')
	    {
	      /* (Some mkdir()'s don't like trailing slashes) */

	      snprintf(f, sizeof(f), "%s/%s", getenv("HOME"), tux_settings_dir);
	    }
	  else
	    {
	      snprintf(f, sizeof(f), "%s/%s/%s",
		       getenv("HOME"), tux_settings_dir, name);
	    }
	}
      else
	{
	  /* WOAH!  Don't know where HOME directory is!  Last resort, use '.'! */

	  strcpy(f, name);
	}
    }
  else
    {
      /* User had overridden save directory with "--savedir" option: */

      if (*name != '\0')
	{
	  /* (Some mkdir()'s don't like trailing slashes) */

	  snprintf(f, sizeof(f), "%s/%s", savedir, name);
	}
      else
	{
	  snprintf(f, sizeof(f), "%s", savedir);
	}
    }
#endif

  return strdup(f);
}


/* Prompt the user with a yes/no question: */

static int do_prompt(const char * const text, const char * const btn_yes, const char * const btn_no)
{
  return(do_prompt_image(text, btn_yes, btn_no, NULL, NULL, NULL));
}


static int do_prompt_snd(const char * const text, const char * const btn_yes, const char * const btn_no, int snd)
{
  return(do_prompt_image_flash_snd(text, btn_yes, btn_no, NULL, NULL, NULL, 0, snd));
}

static int do_prompt_image(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3)
{
  return(do_prompt_image_snd(text, btn_yes, btn_no, img1, img2, img3, SND_NONE));
}

static int do_prompt_image_snd(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3, int snd)
{
  return(do_prompt_image_flash_snd(text, btn_yes, btn_no, img1, img2, img3, 0, snd));
}

static int do_prompt_image_flash(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3, int animate)
{
  return(do_prompt_image_flash_snd(text, btn_yes, btn_no, img1, img2, img3, animate, SND_NONE));
}

static int do_prompt_image_flash_snd(const char * const text, const char * const btn_yes, const char * const btn_no, SDL_Surface * img1, SDL_Surface * img2, SDL_Surface * img3, int animate, int snd)
{
  SDL_Event event;
  SDL_Rect dest;
  int done, ans, w, counter;
  SDL_Color black = {0, 0, 0, 0};
  SDLKey key;
  SDLKey key_y, key_n;
  char *keystr;
#ifndef NO_PROMPT_SHADOWS
  int i;
  SDL_Surface * alpha_surf;
#endif
  int img1_w, img2_w, img3_w, max_img_w, img_x, img_y, offset;


  /* FIXME: Move elsewhere! Or not?! */

  keystr = textdir(gettext("Yes"));
  key_y = tolower(keystr[0]);
  free(keystr);

  keystr = textdir(gettext("No"));
  key_n = tolower(keystr[0]);
  free(keystr);


  do_setcursor(cursor_arrow);


  /* Move cursor automatically if in keymouse mode: */

  if (keymouse)
    {
      mouse_x = WINDOW_WIDTH / 2;
      mouse_y = WINDOW_HEIGHT / 2;
      SDL_WarpMouse(mouse_x, mouse_y);
    }


  /* Draw button box: */

  playsound(0, SND_PROMPT, 1);
  playsound(1, snd, 1);

  for (w = 0; w <= 96; w = w + 4)
    {
      dest.x = 160 + 96 - w + PROMPTOFFSETX;
      dest.y = 94 + 96 - w + PROMPTOFFSETY;
      dest.w = (320 - 96 * 2) + w * 2;
      dest.h = w * 2;
      SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 0, 0, 0));

      SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h);
      SDL_Delay(10);
    }
 

#ifndef NO_PROMPT_SHADOWS
  alpha_surf = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_SRCALPHA,
		                    (320 - 96 * 2) + (w - 4) * 2,
				    (w - 4) * 2,
				    screen->format->BitsPerPixel,
				    screen->format->Rmask,
				    screen->format->Gmask,
				    screen->format->Bmask,
				    screen->format->Amask);

  if (alpha_surf != NULL)
    {
      SDL_FillRect(alpha_surf, NULL, SDL_MapRGB(alpha_surf->format, 0, 0, 0));
      SDL_SetAlpha(alpha_surf, SDL_SRCALPHA, 64);

      for (i = 8; i > 0; i = i - 2)
	{
	  dest.x = 160 + 96 - (w - 4) + i + PROMPTOFFSETX;
	  dest.y = 94 + 96 - (w - 4) + i + PROMPTOFFSETY;
	  dest.w = (320 - 96 * 2) + (w - 4) * 2;
	  dest.h = (w - 4) * 2;
      
	  SDL_BlitSurface(alpha_surf, NULL, screen, &dest);
	}
  
      SDL_FreeSurface(alpha_surf);
    }
#endif
  

  w = w - 6;

  dest.x = 160 + 96 - w + PROMPTOFFSETX;
  dest.y = 94 + 96 - w + PROMPTOFFSETY;
  dest.w = (320 - 96 * 2) + w * 2;
  dest.h = w * 2;
  SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 255, 255, 255));


  /* If we're showing any images on the right, determine the widest width
     for them: */

  /* FIXME: This needs to be fixed for right-to-left interfaces! */

  offset = img1_w = img2_w = img3_w = 0;
  
  if (img1 != NULL)
    img1_w = img1->w;
  if (img2 != NULL)
    img2_w = img2->w;
  if (img3 != NULL)
    img3_w = img3->w;

  max_img_w = max(img1_w, max(img2_w, img3_w));

  if (max_img_w > 0)
    offset = max_img_w + 8;
  
  
  /* Draw the question: */

  wordwrap_text(text, black,
		166 + PROMPTOFFSETX, 100 + PROMPTOFFSETY,
		475 + PROMPTOFFSETX - offset,
		1);


  /* Draw the images (if any, and if not animated): */

  img_x = 457 + PROMPTOFFSETX - offset;
  img_y = 100 + PROMPTOFFSETY + 4;

  if (img1 != NULL)
  {
    dest.x = img_x + (max_img_w - img1->w) / 2;
    dest.y = img_y;

    SDL_BlitSurface(img1, NULL, screen, &dest);

    if (!animate)
      img_y = img_y + img1->h + 4;
  }

  if (!animate)
  {
    if (img2 != NULL)
    {
      dest.x = img_x + (max_img_w - img2->w) / 2;
      dest.y = img_y;

      SDL_BlitSurface(img2, NULL, screen, &dest);

      img_y = img_y + img2->h + 4;
    }

    if (img3 != NULL)
    {
      dest.x = img_x + (max_img_w - img3->w) / 2;
      dest.y = img_y;

      SDL_BlitSurface(img3, NULL, screen, &dest);

      img_y = img_y + img3->h + 4;  // unnecessary
    }
  }


  /* Draw yes button: */

  dest.x = 166 + PROMPTOFFSETX;
  dest.y = 178 + PROMPTOFFSETY;
  SDL_BlitSurface(img_yes, NULL, screen, &dest);


  /* (Bound to UTF8 domain, so always ask for UTF8 rendering!) */

  wordwrap_text(btn_yes, black, 166 + PROMPTOFFSETX + 48 + 4,
		183 + PROMPTOFFSETY, 475 + PROMPTOFFSETX, 1);


  /* Draw no button: */

  if (strlen(btn_no) != 0)
    {
      dest.x = 166 + PROMPTOFFSETX;
      dest.y = 230 + PROMPTOFFSETY;
      SDL_BlitSurface(img_no, NULL, screen, &dest);

      wordwrap_text(btn_no, black,
		    166 + PROMPTOFFSETX + 48 + 4, 235 + PROMPTOFFSETY,
		    475 + PROMPTOFFSETX, 1);
    }


  /* Draw Tux, waiting... */

  draw_tux_text(TUX_BORED, "", 0);

  SDL_Flip(screen);

  done = 0;
  counter = 0;
  ans = 0;

  do
    {
      while (mySDL_PollEvent(&event))
      {
        if (event.type == SDL_QUIT)
	  {
	    ans = 0;
	    done = 1;
	  }
        else if (event.type == SDL_ACTIVEEVENT)
	  {
	    handle_active(&event);
	  }
        else if (event.type == SDL_KEYUP)
	  {
	    key = event.key.keysym.sym;

	    handle_keymouse(key, SDL_KEYUP);
	  }
        else if (event.type == SDL_KEYDOWN)
	  {
	    key = event.key.keysym.sym;

	    handle_keymouse(key, SDL_KEYDOWN);


	    /* FIXME: Should use SDLK_{c} instead of '{c}'?  How? */

	    if (key == key_y || key == SDLK_RETURN)
	      {
	        /* Y or ENTER - Yes! */
	
	        ans = 1;
	        done = 1;
	      }
	    else if (key == key_n || key == SDLK_ESCAPE)
	      {
	        /* N or ESCAPE - No! */

	        if (strlen(btn_no) != 0)
		  {
		    ans = 0;
		    done = 1;
		  }
	        else
		  {
		    if (key == SDLK_ESCAPE)
		    {
		      /* ESCAPE also simply dismisses if there's no Yes/No
		         choice: */
	
		      ans = 1;
		      done = 1;
		    }
		  }
	      }
	  }
        else if (event.type == SDL_MOUSEBUTTONDOWN &&
	         valid_click(event.button.button))
	  {
	    if (event.button.x >= 166 + PROMPTOFFSETX &&
	        event.button.x < 166 + PROMPTOFFSETX + 48)
	      {
	        if (event.button.y >= 178 + PROMPTOFFSETY &&
		    event.button.y < 178 + PROMPTOFFSETY + 48)
		  {
		    ans = 1;
		    done = 1;
		  }
	        else if (strlen(btn_no) != 0 &&
		         event.button.y >= 230 + PROMPTOFFSETY &&
		         event.button.y < 230 + PROMPTOFFSETY + 48)
		  {
		    ans = 0;
		    done = 1;
		  }
	      }
	  }
        else if (event.type == SDL_MOUSEMOTION)
	  { 
	    if (event.button.x >= 166 + PROMPTOFFSETX &&
	        event.button.x < 166 + 48 + PROMPTOFFSETX &&
	        ((event.button.y >= 178 + PROMPTOFFSETY &&
		  event.button.y < 178 + 48 + PROMPTOFFSETY) ||
	         (strlen(btn_no) != 0 &&
		  event.button.y >= 230 && event.button.y < 230 + 48)))
	      {
	        do_setcursor(cursor_hand);
	      }
	    else
	      {
	        do_setcursor(cursor_arrow);
	      }
	  }
      }

      SDL_Delay(100);

      if (animate)
      {
        counter++;

	if (counter == 5)
	{
          dest.x = img_x + (max_img_w - img2->w) / 2;
          dest.y = img_y;

          SDL_BlitSurface(img2, NULL, screen, &dest);
	  SDL_Flip(screen);
	}
	else if (counter == 10)
	{
	  if (img3 != NULL)
	  {
            dest.x = img_x + (max_img_w - img3->w) / 2;
            dest.y = img_y;

            SDL_BlitSurface(img3, NULL, screen, &dest);
	    SDL_Flip(screen);
	  }
	  else
	    counter = 15;
	}

	if (counter == 15)
	{
          dest.x = img_x + (max_img_w - img1->w) / 2;
          dest.y = img_y;

          SDL_BlitSurface(img1, NULL, screen, &dest);
	  SDL_Flip(screen);

	  counter = 0;
	}
      }
    }
  while (!done);


  /* FIXME: Sound effect! */
  /* ... */


  /* Erase question prompt: */

  update_canvas(0, 0, canvas->w, canvas->h);

  return ans;
}


/* Free memory and prepare to quit: */

static void cleanup(void)
{
  int i;

  for (i = 0; i < num_stamps; i++)
    {
#ifndef NOSOUND
      if (stamp_data[i]->ssnd)
        {
	  Mix_FreeChunk(stamp_data[i]->ssnd);
	  stamp_data[i]->ssnd = NULL;
	}
#endif
      if (stamp_data[i]->stxt)
	{
	  free(stamp_data[i]->stxt);
	  stamp_data[i]->stxt = NULL;
	}
      free_surface( &stamp_data[i]->thumbnail );
        
      free(stamp_data[i]->stampname);
      free(stamp_data[i]);
      stamp_data[i] = NULL;
    }
  free(stamp_data);
  free_surface( &active_stamp );

  free_surface_array( img_brushes, num_brushes );
  free_surface_array( img_tools, NUM_TOOLS );
  free_surface_array( img_tool_names, NUM_TOOLS );
  free_surface_array( img_title_names, NUM_TITLES );
  free_surface_array( img_magics, NUM_MAGICS );
  free_surface_array( img_magic_names, NUM_MAGICS );
  free_surface_array( img_shapes, NUM_SHAPES );
  free_surface_array( img_shape_names, NUM_SHAPES );
  free_surface_array( img_tux, NUM_TIP_TUX );

  free_surface( &img_openlabels_open );
  free_surface( &img_openlabels_erase );
  free_surface( &img_openlabels_back );

  free_surface( &img_progress );

  free_surface( &img_yes );
  free_surface( &img_no );

  free_surface( &img_title_on );
  free_surface( &img_title_off );
  free_surface( &img_title_large_on );
  free_surface( &img_title_large_off );

  free_surface( &img_open );
  free_surface( &img_erase );
  free_surface( &img_back );
  free_surface( &img_trash );

  free_surface( &img_dead40x40 );

  free_surface( &img_printer );
  free_surface( &img_printer_wait );
  free_surface( &img_save_over );

  free_surface( &img_btn_up );
  free_surface( &img_btn_down );
  free_surface( &img_btn_off );

  free_surface( &img_cursor_up );
  free_surface( &img_cursor_down );

  free_surface( &img_cursor_starter_up );
  free_surface( &img_cursor_starter_down );

  free_surface( &img_scroll_up );
  free_surface( &img_scroll_down );
  free_surface( &img_scroll_up_off );
  free_surface( &img_scroll_down_off );

  free_surface( &img_grow );
  free_surface( &img_shrink );

  free_surface( &img_bold );
  free_surface( &img_italic );

  free_surface( &img_sparkles );
  free_surface( &img_grass );

  free_surface_array( undo_bufs, NUM_UNDO_BUFS );

#ifdef LOW_QUALITY_COLOR_SELECTOR
  free_surface( &img_paintcan );
#else
  free_surface_array( img_color_btns, NUM_COLORS*2 );
#endif

  free_surface( &screen );
  free_surface( &img_starter );
  free_surface( &img_starter_bkgd );
  free_surface( &canvas );
  free_surface( &img_cur_brush );

  if (medium_font != NULL)
    {
      TTF_CloseFont(medium_font);
      medium_font = NULL;
    }

  if (small_font != NULL)
    {
      TTF_CloseFont(small_font);
      small_font = NULL;
    }

  if (large_font != NULL)
    {
      TTF_CloseFont(large_font);
      large_font = NULL;
    }

#ifdef FORKED_FONTS
  free(user_font_families); // we'll leak the bodies... oh well
#else
  for (i = 0; i < num_font_families; i++)
    {
      if (user_font_families[i])
	{
	  char ** cpp = user_font_families[i]->filename - 1;
	  if (*++cpp)
	    free(*cpp);
	  if (*++cpp)
	    free(*cpp);
	  if (*++cpp)
	    free(*cpp);
	  if (*++cpp)
	    free(*cpp);
	  if (user_font_families[i]->handle)
	    TTF_CloseFont(user_font_families[i]->handle);
	  free(user_font_families[i]->directory);
	  free(user_font_families[i]->family);
	  free(user_font_families[i]);
	  user_font_families[i] = NULL;
	}
    }
#endif

#ifndef NOSOUND
  if (use_sound)
    {
      for (i = 0; i < NUM_SOUNDS; i++)
	{
	  if (sounds[i])
	    {
	      Mix_FreeChunk(sounds[i]);
	      sounds[i] = NULL;
	    }
	}

      Mix_CloseAudio();
    }
#endif


  free_cursor(&cursor_hand);
  free_cursor(&cursor_arrow);
  free_cursor(&cursor_watch);
  free_cursor(&cursor_up);
  free_cursor(&cursor_down);
  free_cursor(&cursor_tiny);
  free_cursor(&cursor_crosshair);
  free_cursor(&cursor_brush);
  free_cursor(&cursor_wand);
  free_cursor(&cursor_insertion);
  free_cursor(&cursor_rotate);
  

  /* (Just in case...) */
  
  SDL_WM_GrabInput(SDL_GRAB_OFF);


  /* Close recording or playback file: */

  if (demofi != NULL)
    {
      fclose(demofi);
    }


  /* If we're using a lockfile, we can 'clear' it when we quit
     (so Tux Paint can be launched again soon, if the user wants to!) */

  if (ok_to_use_lockfile)
  {
    char * lock_fname;
    time_t zero_time;
    FILE * fi;

    lock_fname = get_fname("lockfile.dat");

    zero_time = (time_t) 0;

    fi = fopen(lock_fname, "w");
    if (fi != NULL)
    {
      /* If we can write to it, do so! */

      fwrite(&zero_time, sizeof(time_t), 1, fi);
      fclose(fi);
    }
    else
    {
      fprintf(stderr,
 	      "\nWarning: I couldn't create the lockfile (%s)\n"
	      "The error that occurred was:\n"
	      "%s\n\n", lock_fname, strerror(errno));
    }

    free(lock_fname);
  }

  /* Close up! */

  TTF_Quit();
  SDL_Quit();

}


static void free_cursor(SDL_Cursor ** cursor)
{
  if (*cursor)
    {
      SDL_FreeCursor(*cursor);
      *cursor = NULL;
    }
}


static void free_surface(SDL_Surface **surface_array)
{
  if (*surface_array)
    {
      SDL_FreeSurface(*surface_array);
      *surface_array = NULL;
    }
}


static void free_surface_array(SDL_Surface *surface_array[], int count)
{
  int i;

  for (i = 0; i < count; ++i)
    {
      free_surface(&surface_array[i]);
    }
}


/* Update screen where shape is/was: */

// FIXME: unused
/*
static void update_shape(int cx, int ox1, int ox2, int cy, int oy1, int oy2, int fix)
{
  int rx, ry;

  rx = abs(ox1 - cx);
  if (abs(ox2 - cx) > rx)
    rx = abs(ox2 - cx);

  ry = abs(oy1 - cy);
  if (abs(oy2 - cy) > ry)
    ry = abs(oy2 - cy);

  if (fix)
    {
      if (ry > rx)
	rx = ry;
      else
	ry = rx;
    }

  SDL_UpdateRect(screen, max((cx - rx), 0) + 96, max(cy - ry, 0),
		 min((cx + rx) + 96, screen->w),
		 min(cy + ry, screen->h));
}
*/


/* Draw a shape! */

static void do_shape(int cx, int cy, int ox, int oy, int rotn, int use_brush)
{
  int side, angle_skip, init_ang, rx, ry, rmax, x1, y1, x2, y2, xp, yp,
    old_brush, step;
  float a1, a2, rotn_rad;
  int xx;


  /* Determine radius/shape of the shape to draw: */

  old_brush = 0;
  rx = ox - cx;
  ry = oy - cy;


  /* If the shape has a 1:1 ("locked") aspect ratio, use the larger radius: */

  if (shape_locked[cur_shape])
    {
      if (rx > ry)
	ry = rx;
      else
	rx = ry;
    }


  /* Is the shape tiny?  Make it SOME size, first! */

  if (rx < 15 && ry < 15)
  {
    rx = 15;
    ry = 15;
  }


  /* Render a default brush: */

  if (use_brush)
    {
      old_brush = cur_brush;
      cur_brush = 0;  /* Kludgy! */
      render_brush();
    }


  /* Draw the shape: */

  angle_skip = 360 / shape_sides[cur_shape];

  init_ang = shape_init_ang[cur_shape];


  step = 1;

  if (dont_do_xor && !use_brush)
    {
      /* If we're in light outline mode & not drawing the shape with the brush,
	 if it has lots of sides (like a circle), reduce the number of sides: */

      if (shape_sides[cur_shape] > 5)
	step = (shape_sides[cur_shape] / 8);
    }


  for (side = 0; side < shape_sides[cur_shape]; side = side + step)
    {
      a1 = (angle_skip * side + init_ang) * M_PI / 180;
      a2 = (angle_skip * (side + 1) + init_ang) * M_PI / 180;

      x1 = (int) (cos(a1) * rx);
      y1 = (int) (-sin(a1) * ry);

      x2 = (int) (cos(a2) * rx);
      y2 = (int) (-sin(a2) * ry);


      /* Rotate the line: */

      if (rotn != 0)
	{
	  rotn_rad = rotn * M_PI / 180;

	  xp = x1 * cos(rotn_rad) - y1 * sin(rotn_rad);
	  yp = x1 * sin(rotn_rad) + y1 * cos(rotn_rad);

	  x1 = xp;
	  y1 = yp;

	  xp = x2 * cos(rotn_rad) - y2 * sin(rotn_rad);
	  yp = x2 * sin(rotn_rad) + y2 * cos(rotn_rad);

	  x2 = xp;
	  y2 = yp;
	}


      /* Center the line around the center of the shape: */

      x1 = x1 + cx;
      y1 = y1 + cy;
      x2 = x2 + cx;
      y2 = y2 + cy;


      /* Draw: */

      if (!use_brush)
	{
	  /* (XOR) */

	  line_xor(x1, y1, x2, y2);
	}
      else
	{
	  /* Brush */

	  brush_draw(x1, y1, x2, y2, 0);
	}
    }


  if (use_brush && shape_filled[cur_shape])
    {
      /* FIXME: In the meantime, we'll do this lame radius-based fill: */

      for (xx = abs(rx); xx >= 0; xx--)
	{
	  for (side = 0; side < shape_sides[cur_shape]; side++)
	    {
	      a1 = (angle_skip * side + init_ang) * M_PI / 180;
	      a2 = (angle_skip * (side + 1) + init_ang) * M_PI / 180;

	      x1 = (int) (cos(a1) * xx);
	      y1 = (int) (-sin(a1) * ry);

	      x2 = (int) (cos(a2) * xx);
	      y2 = (int) (-sin(a2) * ry);


	      /* Rotate the line: */

	      if (rotn != 0)
		{
		  rotn_rad = rotn * M_PI / 180;

		  xp = x1 * cos(rotn_rad) - y1 * sin(rotn_rad);
		  yp = x1 * sin(rotn_rad) + y1 * cos(rotn_rad);

		  x1 = xp;
		  y1 = yp;

		  xp = x2 * cos(rotn_rad) - y2 * sin(rotn_rad);
		  yp = x2 * sin(rotn_rad) + y2 * cos(rotn_rad);

		  x2 = xp;
		  y2 = yp;
		}


	      /* Center the line around the center of the shape: */

	      x1 = x1 + cx;
	      y1 = y1 + cy;
	      x2 = x2 + cx;
	      y2 = y2 + cy;


	      /* Draw: */

	      brush_draw(x1, y1, x2, y2, 0);
	    }

	  if (xx % 10 == 0)
	    update_canvas(0, 0, WINDOW_WIDTH - 96, (48 * 7) + 40 + HEIGHTOFFSET);
	}
    }


  /* Update it! */

  if (use_brush)
    {
      if (abs(rx) > abs(ry))
	rmax = abs(rx) + 20;
      else
	rmax = abs(ry) + 20;

      update_canvas(cx - rmax, cy - rmax,
		    cx + rmax, cy + rmax);
    }


  /* Return to normal brush (for paint brush and line tools): */

  if (use_brush)
    {
      cur_brush = old_brush;
      render_brush();
    }
}


/* What angle is the mouse away from the center of a shape? */

static int rotation(int ctr_x, int ctr_y, int ox, int oy)
{
  return(atan2(oy - ctr_y, ox - ctr_x) * 180 / M_PI);
}


/* FIXME: Move elsewhere!!! */

#define PROMPT_SAVE_OVER_TXT gettext_noop("Save over the older version of this picture?")
#define PROMPT_SAVE_OVER_YES gettext_noop("Yes")
#define PROMPT_SAVE_OVER_NO  gettext_noop("No, save a new file")


/* Save the current image: */

static int do_save(void)
{
  int res;
  char * fname;
  char tmp[1024];
  SDL_Surface * thm;
#ifndef SAVE_AS_BMP
  FILE * fi;
#endif


  /* Was saving completely disabled? */

  if (disable_save)
    return 0;

  
  if (promptless_save == SAVE_OVER_NO)
    {
      /* Never save over - _always_ save a new file! */

      get_new_file_id();
    }
  else if (promptless_save == SAVE_OVER_PROMPT)
    {
      /* Saving the same picture? */

      if (file_id[0] != '\0')
	{
	  /* We sure we want to do that? */

	  if (do_prompt_image_snd(PROMPT_SAVE_OVER_TXT,
				  PROMPT_SAVE_OVER_YES,
				  PROMPT_SAVE_OVER_NO,
				  img_save_over, NULL, NULL,
				  SND_AREYOUSURE) == 0)
	    {
	      /* No - Let's save a new picture! */

	      get_new_file_id();
	    }
	}
      else
	{
	  /* Saving a new picture: */
	
	  get_new_file_id();
	}
    }
  else if (promptless_save == SAVE_OVER_ALWAYS)
    {
      if (file_id[0] == '\0')
	get_new_file_id();
    }
  

  /* Make sure we have a ~/.tuxpaint directory: */

  show_progress_bar();
  do_setcursor(cursor_watch);

  fname = get_fname("");

  res = mkdir(fname, 0755);

  if (res != 0 && errno != EEXIST)
    {
      fprintf(stderr,
	      "\nError: Can't create user data directory:\n"
	      "%s\n"
	      "The error that occurred was:\n"
	      "%s\n\n", fname, strerror(errno));

      fprintf(stderr,
	      "Cannot save the any pictures! SORRY!\n\n");

      draw_tux_text(TUX_OOPS, SDL_GetError(), 0);

      free(fname);
      return 0;
    }
  free(fname);

  show_progress_bar();

  
  /* Make sure we have a ~/.tuxpaint/saved directory: */

  fname = get_fname("saved");

  res = mkdir(fname, 0755);

  if (res != 0 && errno != EEXIST)
    {
      fprintf(stderr,
	      "\nError: Can't create user data directory:\n"
	      "%s\n"
	      "The error that occurred was:\n"
	      "%s\n\n", fname, strerror(errno));

      fprintf(stderr,
	      "Cannot save any pictures! SORRY!\n\n");

      draw_tux_text(TUX_OOPS, SDL_GetError(), 0);

      free(fname);
      return 0;
    }
  free(fname);

  show_progress_bar();


  /* Make sure we have a ~/.tuxpaint/saved/.thumbs/ directory: */

  fname = get_fname("saved/.thumbs");

  res = mkdir(fname, 0755);

  if (res != 0 && errno != EEXIST)
    {
      fprintf(stderr,
	      "\nError: Can't create user data thumbnail directory:\n"
	      "%s\n"
	      "The error that occurred was:\n"
	      "%s\n\n", fname, strerror(errno));

      fprintf(stderr,
	      "Cannot save any pictures! SORRY!\n\n");

      draw_tux_text(TUX_OOPS, SDL_GetError(), 0);

      free(fname);
      return 0;
    }
  free(fname);

  show_progress_bar();

  
  /* Save the file: */

  snprintf(tmp, sizeof(tmp), "saved/%s%s", file_id, FNAME_EXTENSION);
  fname = get_fname(tmp);
  debug(fname);

#ifdef SAVE_AS_BMP
  if (SDL_SaveBMP(canvas, fname))
    {
      fprintf(stderr,
	      "\nError: Couldn't save the current image!\n"
	      "%s\n"
	      "The Simple DirectMedia Layer error that occurred was:\n"
	      "%s\n\n", fname, SDL_GetError());

      draw_tux_text(TUX_OOPS, SDL_GetError(), 0);

      free(fname);
      return 0;
    }
  else
    {
      /* Ta-Da! */

      playsound(0, SND_SAVE, 1);
      draw_tux_text(TUX_DEFAULT, tool_tips[TOOL_SAVE], 1);
    }
#else
  fi = fopen(fname, "wb");
  if (fi == NULL)
    {
      fprintf(stderr,
	      "\nError: Couldn't save the current image!\n"
	      "%s\n"
	      "The system error that occurred was:\n"
	      "%s\n\n",
	      fname, strerror(errno));

      draw_tux_text(TUX_OOPS, strerror(errno), 0);
    }
  else
    {
      if (!do_png_save(fi, fname, canvas))
	{
	  free(fname);
	  return 0;
	}
    }
#endif

  free(fname);
  
  show_progress_bar();
  
  
  /* Save thumbnail, too: */

  /* (Was thumbnail in old directory, rather than under .thumbs?) */
  
  snprintf(tmp, sizeof(tmp), "saved/%s-t%s", file_id, FNAME_EXTENSION);
  fname = get_fname(tmp);
  fi = fopen(fname, "r");
  if (fi != NULL)
    {
      fclose(fi);
    }
  else
    {
      /* No old thumbnail!  Save this image's thumbnail in the new place,
	 under ".thumbs" */
  
      snprintf(tmp, sizeof(tmp), "saved/.thumbs/%s-t%s", file_id, FNAME_EXTENSION);
      fname = get_fname(tmp);
    }
  
  debug(fname);

  thm = thumbnail(canvas, THUMB_W - 20, THUMB_H - 20, 0);
  
  fi = fopen(fname, "wb");
  if (fi == NULL)
    {
      fprintf(stderr, "\nError: Couldn't save thumbnail of image!\n"
	      "%s\n"
	      "The system error that occurred was:\n"
	      "%s\n\n",
	      fname, strerror(errno));
    }
  else
    {
      do_png_save(fi, fname, thm);
    }
  SDL_FreeSurface(thm);
  
  free(fname);


  /* Write 'starter' info, if any: */

  if (starter_id[0] != '\0')
  {
    snprintf(tmp, sizeof(tmp), "saved/%s.dat", file_id);
    fname = get_fname(tmp);
    fi = fopen(fname, "w");
    if (fi != NULL)
    {
      fprintf(fi, "%s\n", starter_id);
      fprintf(fi, "%d %d\n", starter_mirrored, starter_flipped);
      fclose(fi);
    }

    free(fname);
  }
  

  /* All happy! */

  playsound(0, SND_SAVE, 1);
  draw_tux_text(TUX_DEFAULT, tool_tips[TOOL_SAVE], 1);
  do_setcursor(cursor_arrow);

  return 1;
}


/* Actually save the PNG data to the file stream: */
static int do_png_save(FILE * fi, const char * const fname, SDL_Surface * surf)
{
  png_structp png_ptr;
  png_infop info_ptr;
  png_text text_ptr[4];
  unsigned char ** png_rows;
  Uint8 r, g, b;
  int x, y, count;
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[surf->format->BytesPerPixel];
 

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (png_ptr == NULL)
    {
      fclose(fi);
      png_destroy_write_struct(&png_ptr, (png_infopp) NULL);

      fprintf(stderr, "\nError: Couldn't save the image!\n%s\n\n", fname);
      draw_tux_text(TUX_OOPS, strerror(errno), 0);
    }
  else
    {
      info_ptr = png_create_info_struct(png_ptr);
      if (info_ptr == NULL)
	{
	  fclose(fi);
	  png_destroy_write_struct(&png_ptr, (png_infopp) NULL);

	  fprintf(stderr, "\nError: Couldn't save the image!\n%s\n\n", fname);
	  draw_tux_text(TUX_OOPS, strerror(errno), 0);
	}
      else
	{
	  if (setjmp(png_jmpbuf(png_ptr)))
	    {
	      fclose(fi);
	      png_destroy_write_struct(&png_ptr, (png_infopp) NULL);

	      fprintf(stderr, "\nError: Couldn't save the image!\n%s\n\n", fname);
	      draw_tux_text(TUX_OOPS, strerror(errno), 0);
  
	      return 0;
	    }
	  else
	    {
	      png_init_io(png_ptr, fi);

	      info_ptr->width = surf->w;
	      info_ptr->height = surf->h;
	      info_ptr->bit_depth = 8;
	      info_ptr->color_type = PNG_COLOR_TYPE_RGB;
	      info_ptr->interlace_type = 1;
	      info_ptr->valid = 0;  // will be updated by various png_set_FOO() functions

              png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);

	      /* Set headers */

	      count = 0;

	      /*
	      if (title != NULL && strlen(title) > 0)
	      {
	        text_ptr[count].key = "Title";
	        text_ptr[count].text = title;
	        text_ptr[count].compression = PNG_TEXT_COMPRESSION_NONE;
	        count++;
	      }
	      */

	      text_ptr[count].key = (png_charp) "Software";
	      text_ptr[count].text =
	        (png_charp) "Tux Paint " VER_VERSION " (" VER_DATE ")";
	      text_ptr[count].compression = PNG_TEXT_COMPRESSION_NONE;
	      count++;

	      
	      png_set_text(png_ptr, info_ptr, text_ptr, count);
			      
	      png_write_info(png_ptr, info_ptr);



	      /* Save the picture: */

	      png_rows = malloc(sizeof(char *) * surf->h);

	      for (y = 0; y < surf->h; y++)
		{
		  png_rows[y] = malloc(sizeof(char) * 3 * surf->w);

		  for (x = 0; x < surf->w; x++)
		    {
		      SDL_GetRGB(getpixel(surf, x, y), surf->format, &r, &g, &b);

		      png_rows[y][x * 3 + 0] = r;
		      png_rows[y][x * 3 + 1] = g;
		      png_rows[y][x * 3 + 2] = b;
		    }
		}
        
	      png_write_image(png_ptr, png_rows);

	      for (y = 0; y < surf->h; y++)
		free(png_rows[y]);

	      free(png_rows);
        

	      png_write_end(png_ptr, NULL);

	      png_destroy_write_struct(&png_ptr, &info_ptr);
	      fclose(fi);

	      return 1;
	    }
	}
    }

  return 0;
}

///////////////////////////////////// PostScript printing ///////////
#ifdef PRINTMETHOD_PS

/* Actually save the PostScript data to the file stream: */
static int do_ps_save(FILE * fi, const char *restrict  const fname, SDL_Surface * surf)
{
  unsigned char *restrict const ps_row = malloc(surf->w * 3);
  int x, y;
  char buf[256];
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[surf->format->BytesPerPixel];
  time_t t = time(NULL);
  
  fprintf(fi, "%%!PS-Adobe-3.0 EPSF-3.0\n");  // probably broken, but close enough maybe
  fprintf(fi, "%%%%Title: (%s)\n", fname);
  strftime(buf, sizeof buf - 1, "%a %b %e %H:%M:%S %Y", localtime(&t));
  fprintf(fi, "%%%%CreationDate: (%s)\n", buf);
  fprintf(fi, "%%%%Creator: (Tux Paint " VER_VERSION ", " VER_DATE ")\n");
  fprintf(fi, "%%%%LanguageLevel: 2\n");
//  fprintf(fi, "%%%%BoundingBox: 72 214 540 578\n");  // doubt we have the needed info
  fprintf(fi, "%%%%DocumentData: Binary\n");
  fprintf(fi, "%%%%EndComments\n");
  fprintf(fi, "\n");
  fprintf(fi, "gsave\n");
  fprintf(fi, "\n");
//  fprintf(fi, "90 rotate\n");  // landscape mode
  fprintf(fi, "%% First, grab the page size.\n");
  fprintf(fi, "gsave\n");
  fprintf(fi, "  clippath\n");
  fprintf(fi, "  pathbbox\n");
  fprintf(fi, "grestore\n");
  fprintf(fi, "/ury exch def\n");
  fprintf(fi, "/urx exch def\n");
  fprintf(fi, "/lly exch def\n");
  fprintf(fi, "/llx exch def\n");
  fprintf(fi, "\n");
  fprintf(fi, "llx lly translate\n");
  fprintf(fi, "\n");
  fprintf(fi, "/width %u def\n", surf->w);
  fprintf(fi, "/height %u def\n", surf->h);
  fprintf(fi, "width height scale\n");
  fprintf(fi, "\n");
  fprintf(fi, "urx llx sub width div\n");
  fprintf(fi, "ury lly sub height div\n");
  fprintf(fi, "%% now do a 'min' operation\n");
  fprintf(fi, "2 copy gt { exch } if pop\n");
  fprintf(fi, "\n");
  fprintf(fi, "dup scale\n");
  fprintf(fi, "/DeviceRGB setcolorspace\n");
  fprintf(fi, "<<\n");
  fprintf(fi, "  /ImageType 1\n");
  fprintf(fi, "  /Width width /Height height\n");
  fprintf(fi, "  /BitsPerComponent 8\n");
  fprintf(fi, "  /ImageMatrix [width 0 0 height neg 0 height]\n");
  fprintf(fi, "  /Decode [0 1 0 1 0 1]\n");
  fprintf(fi, "  /DataSource currentfile\n");
  fprintf(fi, ">>\n");
  fprintf(fi, "%%%%BeginData: %u Binary Bytes\n", surf->w * surf->h * 3u);
  fprintf(fi, "image\n");

  /* Save the picture: */
  for (y = 0; y < surf->h; y++)
    {
      for (x = 0; x < surf->w; x++)
        {
          Uint8 r, g, b;
          SDL_GetRGB(getpixel(surf, x, y), surf->format, &r, &g, &b);
          ps_row[x * 3 + 0] = r;
          ps_row[x * 3 + 1] = g;
          ps_row[x * 3 + 2] = b;
        }
      fwrite(ps_row,surf->w,3,fi);
    }
  free(ps_row);

  fprintf(fi, "\n");
  fprintf(fi, "%%%%EndData\n");
  fprintf(fi, "grestore\n");
  fprintf(fi, "showpage\n");
  fprintf(fi, "%%%%EOF\n");

  fclose(fi);
  return 1;
}

#endif
/////////////////////////////////////////////////////////////////

/* Pick a new file ID: */
static void get_new_file_id(void)
{
  time_t t;

  t = time(NULL);

  strftime(file_id, sizeof(file_id), "%Y%m%d%H%M%S", localtime(&t));
  debug(file_id);


  /* FIXME: Show thumbnail and prompt for title: */
}


/* Handle quitting (and prompting to save, if necessary!) */

static int do_quit(void)
{
  int done;

  done = do_prompt_snd(PROMPT_QUIT_TXT,
		       PROMPT_QUIT_YES,
		       PROMPT_QUIT_NO,
		       SND_AREYOUSURE);

  if (done && !been_saved && !disable_save)
    {
      if (do_prompt(PROMPT_QUIT_SAVE_TXT,
		    PROMPT_QUIT_SAVE_YES,
		    PROMPT_QUIT_SAVE_NO))
	{
	  if (do_save())
	    {
	      do_prompt(tool_tips[TOOL_SAVE],
			"OK",
			"");
	    }
	  else
	    {
	      /* Couldn't save!  Abort quit! */
	
	      done = 0;
	    }
	}
    }

  return(done);
}



/* Open a saved image: */

#define PLACE_STARTERS_DIR 0
#define PLACE_SAVED_DIR 1
#define NUM_PLACES_TO_LOOK 2


void do_open(void)
{
  SDL_Surface * img, * img1, * img2;
  int things_alloced;
  SDL_Surface * * thumbs = NULL;
  DIR * d;
  struct dirent * f;
  struct dirent2 * fs;
  int place;
  char * dirname[NUM_PLACES_TO_LOOK];
  char * rfname;
  char * * d_names = NULL, * * d_exts = NULL;
  int * d_places;
  FILE * fi;
  char fname[1024];
  char * tmp_fname;
  int num_files, i, done, update_list, want_erase, cur, which,
    num_files_in_dirs, j, res;
  SDL_Rect dest;
  SDL_Event event;
  SDLKey key;
  Uint32 last_click_time;
  int last_click_which, last_click_button;
  int places_to_look;

  do_setcursor(cursor_watch);
  
  /* Allocate some space: */

  things_alloced = 32;
  
  fs = (struct dirent2 *) malloc(sizeof(struct dirent2) * things_alloced);
  
  num_files = 0;
  cur = 0;
  which = 0;
  num_files_in_dirs = 0;


  /* Open directories of images: */

  for (places_to_look = 0;
       places_to_look < NUM_PLACES_TO_LOOK;
       places_to_look++)
  {
    if (places_to_look == PLACE_STARTERS_DIR)
    {
      /* Check for coloring-book style 'starter' images first: */

      dirname[places_to_look] = strdup(DATA_PREFIX "starters");
    }
    else
    {
      /* Then check for saved-images: */

      dirname[places_to_look] = get_fname("saved");
    }
    

    /* Read directory of images and build thumbnails: */

    d = opendir(dirname[places_to_look]);
    
    if (d != NULL)
    {
      /* Gather list of files (for sorting): */
      
      do
	{
	  f = readdir(d);
	  
	  if (f != NULL)
	    {
	      memcpy(&(fs[num_files_in_dirs].f), f, sizeof(struct dirent));
	      fs[num_files_in_dirs].place = places_to_look;
	      
	      num_files_in_dirs++;
	      
	      if (num_files_in_dirs >= things_alloced)
		{
		  things_alloced = things_alloced + 32;
		  fs = (struct dirent2 *) realloc(fs,
				  		  sizeof(struct dirent2) *
						  things_alloced);
		}
	    }
	}
      while (f != NULL);
      
      closedir(d);
    }
  }


    /* (Re)allocate space for the information about these files: */

    thumbs = (SDL_Surface * *) malloc(sizeof(SDL_Surface *) *
		    			num_files_in_dirs);
    d_places = (int *) malloc(sizeof(int) * num_files_in_dirs);
    d_names = (char * *) malloc(sizeof(char *) * num_files_in_dirs);
    d_exts = (char * *) malloc(sizeof(char *) * num_files_in_dirs);


    /* Sort: */
      
    qsort(fs, num_files_in_dirs, sizeof(struct dirent2),
	  (int(*)(const void *, const void *))compare_dirent2s);
      
      
    /* Read directory of images and build thumbnails: */
      
    for (j = 0; j < num_files_in_dirs; j++)
    {
      f = &(fs[j].f);
      place = fs[j].place;

      show_progress_bar();
      
      if (f != NULL)
      {
        debug(f->d_name);
	      
        if (strcasestr(f->d_name, "-t.") == NULL &&
	    strcasestr(f->d_name, "-back.") == NULL)
	{
	  if (strcasestr(f->d_name, FNAME_EXTENSION) != NULL
#ifndef SAVE_AS_BMP
          /* Support legacy BMP files for load: */
		      
	      || strcasestr(f->d_name, ".bmp") != NULL
#endif
	      )
	  {
	    strcpy(fname, f->d_name);
	    if (strcasestr(fname, FNAME_EXTENSION) != NULL)
	    {
	      strcpy(strcasestr(fname, FNAME_EXTENSION), "");
	      d_exts[num_files] = strdup(FNAME_EXTENSION);
	    }
		      
#ifndef SAVE_AS_BMP
	    if (strcasestr(fname, ".bmp") != NULL)
	    {
	      strcpy(strcasestr(fname, ".bmp"), "");
	      d_exts[num_files] = strdup(".bmp");
	    }
#endif
 
	    d_names[num_files] = strdup(fname);
	    d_places[num_files] = place;


	    /* Is it the 'current' file we just loaded?
	       We'll make it the current selection! */
		 
	    if (strcmp(d_names[num_files], file_id) == 0)
	    {
	      which = num_files;
	      cur = (which / 4) * 4;

	      /* Center the cursor (useful for when the last item is
	         selected first!) */

	      if (cur - 8 >= 0)
		cur = cur - 8;
	      else if (cur - 4 >= 0)
		cur = cur - 4;
	    }
		     
    
	    /* Try to load thumbnail first: */

	    snprintf(fname, sizeof(fname), "%s/.thumbs/%s-t.png",
		     dirname[d_places[num_files]], d_names[num_files]);
	    debug(fname);
	    img = IMG_Load(fname);
		      
	    if (img == NULL)
	    {
	      /* No thumbnail in the new location ("saved/.thumbs"),
	         try the old locatin ("saved/"): */
	
	      snprintf(fname, sizeof(fname), "%s/%s-t.png",
		       dirname[d_places[num_files]],
		       d_names[num_files]);
              debug(fname);
			  
	      img = IMG_Load(fname);
	    }
	    
	    if (img != NULL)
	    {
	      /* Loaded the thumbnail from one or the other location */
	      show_progress_bar();

	      img1 = SDL_DisplayFormat(img);
	      SDL_FreeSurface(img);

	      // if too big, or too small in both dimensions, rescale it
	      // ( for now: using old thumbnail as source for high speed, low quality)
	      if (img1->w > THUMB_W-20 || img1->h > THUMB_H-20 || (img1->w < THUMB_W-20 && img1->h < THUMB_H-20) )
	        {
	          img2 = thumbnail(img1, THUMB_W - 20, THUMB_H - 20, 0);
	          SDL_FreeSurface(img1);
	          img1 = img2;
	        }

	      thumbs[num_files] = img1;
		      
	      if (thumbs[num_files] == NULL)
	      {
	        fprintf(stderr,
		        "\nError: Couldn't create a thumbnail of "
		        "saved image!\n"
		        "%s\n", fname);
	      }

	      num_files++;
	    }
	    else
	    {
	      /* No thumbnail - load original: */
	      /* (Make sure we have a .../saved/.thumbs/ directory:) */
			  
	      tmp_fname = get_fname("saved/.thumbs");
			  
	      res = mkdir(tmp_fname, 0755);
			  
	      if (res != 0 && errno != EEXIST)
	      {
	        fprintf(stderr,
		        "\nError: Can't create user data thumbnail directory:\n"
		        "%s\n"
		        "The error that occurred was:\n"
		        "%s\n\n", tmp_fname, strerror(errno));
	      }
			  
	      free(tmp_fname);


	      img = NULL;
	      
              if (d_places[num_files] == PLACE_STARTERS_DIR)
	      {
		/* Try to load a starter's background image, first!
		   If it exists, it should give a better idea of what the
		   starter looks like, compared to the overlay image... */

		/* (Try JPEG first) */
		snprintf(fname, sizeof(fname), "%s/%s-back.jpeg",
			 dirname[d_places[num_files]],
		         d_names[num_files]);

		img = IMG_Load(fname);


		if (img == NULL)
		{
		  /* (Try PNG next) */
		  snprintf(fname, sizeof(fname), "%s/%s-back.png",
			   dirname[d_places[num_files]],
		           d_names[num_files]);

		  img = IMG_Load(fname);
	        }
	      }

	      
	      if (img == NULL)
	      {
		/* Didn't load a starter background (or didn't try!),
		   try loading the actual image... */

	        snprintf(fname, sizeof(fname), "%s/%s",
		         dirname[d_places[num_files]], f->d_name);
			 debug(fname);
#ifdef SAVE_AS_BMP
	        img = SDL_LoadBMP(fname);
#else
	        img = IMG_Load(fname);
#endif
	      }

	      
	      show_progress_bar();
			  
	      if (img == NULL)
	      {
	        fprintf(stderr,
		        "\nWarning: I can't open one of the saved files!\n"
		        "%s\n"
		        "The Simple DirectMedia Layer error that "
		        "occurred was:\n"
		        "%s\n\n",
		        fname, SDL_GetError());
			      
		free(d_names[num_files]);
	        free(d_exts[num_files]);
	      }
	      else
	      {
	        /* Turn it into a thumbnail: */
			      
	        img1 = SDL_DisplayFormat(img);
	        img2 = thumbnail(img1, THUMB_W - 20, THUMB_H - 20, 0);
		SDL_FreeSurface(img1);
			      
		show_progress_bar();
			      
		thumbs[num_files] = SDL_DisplayFormat(img2);
		SDL_FreeSurface(img2);
		if (thumbs[num_files] == NULL)
		{
		  fprintf(stderr,
			  "\nError: Couldn't create a thumbnail of "
			  "saved image!\n"
			  "%s\n", fname);
		}
		 
		SDL_FreeSurface(img);
			      
		show_progress_bar();
			      
			      
		/* Let's save this thumbnail, so we don't have to
		   create it again next time 'Open' is called: */
		
		if (d_places[num_files] == PLACE_SAVED_DIR)
		{
		  debug("Saving thumbnail for this one!");

		  snprintf(fname, sizeof(fname), "%s/.thumbs/%s-t.png",
			   dirname[d_places[num_files]], d_names[num_files]);
			      
		  fi = fopen(fname, "wb");
		  if (fi == NULL)
		  {
		    fprintf(stderr,
			    "\nError: Couldn't save thumbnail of "
			    "saved image!\n"
			    "%s\n"
			    "The error that occurred was:\n"
			    "%s\n\n",
			    fname, strerror(errno));
		  }
		  else
		  {
		    do_png_save(fi, fname, thumbs[num_files]);
		  }
		 
		  show_progress_bar();
		}

		 
		num_files++;
	      }
	    }
	  }
	}
	else
	{
	  /* It was a thumbnail file ("...-t.png") or immutable scene starter's
	     overlay layer ("...-front.png") */
	}
      }
    }
      
      
      
#ifdef DEBUG
  printf("%d saved files were found!\n", num_files);
#endif


  
  if (num_files == 0)
  {
    do_prompt_snd(PROMPT_OPEN_NOFILES_TXT, PROMPT_OPEN_NOFILES_YES, "",
		  SND_YOUCANNOT);
  }
  else
  {
    /* Let user choose an image: */

    char *freeme = 
  		textdir(gettext_noop("Choose the picture you want, "
  				     "then click “Open”."));
    draw_tux_text(TUX_BORED,freeme, 1);
    free(freeme);

    /* NOTE: cur is now set above; if file_id'th file is found, it's
       set to that file's index; otherwise, we default to '0' */
    
    update_list = 1;
    want_erase = 0;

    done = 0;

    last_click_which = -1;
    last_click_time = 0;
    last_click_button = -1;


    do_setcursor(cursor_arrow);
  
  
    do
    {
      /* Update screen: */
	  
      if (update_list)
      {
        /* Erase: */
	      
	dest.x = 96;
	dest.y = 0;
	dest.w = WINDOW_WIDTH - 96 - 96;
	dest.h = 48 * 7 + 40 + HEIGHTOFFSET;
	      
	SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format,
					       255, 255, 255));
	      
	 
	/* Draw icons: */
	      
	for (i = cur; i < cur + 16 && i < num_files; i++)
	{
	  /* Draw cursor: */
	 
	  dest.x = THUMB_W * ((i - cur) % 4) + 96;
	  dest.y = THUMB_H * ((i - cur) / 4) + 24;
	 
	  if (d_places[i] == PLACE_SAVED_DIR)
	  {
	    if (i == which)
	    {
	      SDL_BlitSurface(img_cursor_down, NULL, screen, &dest);
	      debug(d_names[i]);
	    }
	    else
	      SDL_BlitSurface(img_cursor_up, NULL, screen, &dest);
	  }
	  else
	  {
	    if (i == which)
	    {
	      SDL_BlitSurface(img_cursor_starter_down, NULL, screen, &dest);
	      debug(d_names[i]);
	    }
	    else
	      SDL_BlitSurface(img_cursor_starter_up, NULL, screen, &dest);
	  }
		  
		  
		  
	  dest.x = THUMB_W * ((i - cur) % 4) + 96 + 10 +
		   (THUMB_W - 20 - thumbs[i]->w) / 2;
          dest.y = THUMB_H * ((i - cur) / 4) + 24 + 10 +
		   (THUMB_H - 20 - thumbs[i]->h) / 2;
		  
	  if (thumbs[i] != NULL)
	    SDL_BlitSurface(thumbs[i], NULL, screen, &dest);
	}
	      
	      
	/* Draw arrows: */
	      
	dest.x = (WINDOW_WIDTH - img_scroll_up->w) / 2;
	dest.y = 0;
	      
	if (cur > 0)
	  SDL_BlitSurface(img_scroll_up, NULL, screen, &dest);
	else
	  SDL_BlitSurface(img_scroll_up_off, NULL, screen, &dest);
	      
	dest.x = (WINDOW_WIDTH - img_scroll_up->w) / 2;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - 48;
	      
	if (cur < num_files - 16)
	  SDL_BlitSurface(img_scroll_down, NULL, screen, &dest);
	else
	  SDL_BlitSurface(img_scroll_down_off, NULL, screen, &dest);
	      
	      
	/* "Open" button: */
	      
	dest.x = 96;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - 48;
	SDL_BlitSurface(img_open, NULL, screen, &dest);
	     
	dest.x = 96 + (48 - img_openlabels_open->w) / 2;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - img_openlabels_open->h;
	SDL_BlitSurface(img_openlabels_open, NULL, screen, &dest);
	      
	      
	/* "Back" button: */
	      
	dest.x = WINDOW_WIDTH - 96 - 48;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - 48;
	SDL_BlitSurface(img_back, NULL, screen, &dest);
	      
	dest.x = WINDOW_WIDTH - 96 - 48 + (48 - img_openlabels_back->w) / 2;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - img_openlabels_back->h;
	SDL_BlitSurface(img_openlabels_back, NULL, screen, &dest);
	     
	      
	/* "Erase" button: */

	dest.x = WINDOW_WIDTH - 96 - 48 - 48;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - 48;
	
	if (d_places[which] != PLACE_STARTERS_DIR)
	  SDL_BlitSurface(img_erase, NULL, screen, &dest);
	else
	  SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
	      
	dest.x = WINDOW_WIDTH - 96 - 48 - 48 + (48 - img_openlabels_erase->w) / 2;
	dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - img_openlabels_erase->h;
	SDL_BlitSurface(img_openlabels_erase, NULL, screen, &dest);
	      
	      
	SDL_Flip(screen);
	      
	update_list = 0;
      }
	  
	  
      mySDL_WaitEvent(&event);
	  
      if (event.type == SDL_QUIT)
      {
	done = 1;

	/* FIXME: Handle SDL_Quit better */
      }
      else if (event.type == SDL_ACTIVEEVENT)
      {
	handle_active(&event);
      }
      else if (event.type == SDL_KEYUP)
      {
	key = event.key.keysym.sym;
	      
	handle_keymouse(key, SDL_KEYUP);
      }
      else if (event.type == SDL_KEYDOWN)
      {
	key = event.key.keysym.sym;
	      
	handle_keymouse(key, SDL_KEYDOWN);
	      
	if (key == SDLK_LEFT)
	{
	  if (which > 0)
	  {
	    which--;
		      
	    if (which < cur)
	      cur = cur - 4;
		      
	    update_list = 1;
	  }
	}
	else if (key == SDLK_RIGHT)
	{
	  if (which < num_files - 1)
	  {
	    which++;
		      
	    if (which >= cur + 16)
	      cur = cur + 4;
		      
	    update_list = 1;
	  }
	}
	else if (key == SDLK_UP)
	{
	  if (which >= 0)
	  {
	    which = which - 4;
		      
	    if (which < 0)
	      which = 0;
		      
	    if (which < cur)
	      cur = cur - 4;
		      
	    update_list = 1;
	  }
	}
        else if (key == SDLK_DOWN)
	{
	      if (which < num_files)
		{
		  which = which + 4;
	      
		  if (which >= num_files)
		    which = num_files - 1;
	      
		  if (which >= cur + 16)
		    cur = cur + 4;
	      
		  update_list = 1;
		}
	}
	else if (key == SDLK_RETURN || key == SDLK_SPACE)
	  {
	    /* Open */
	
	    done = 1;
	    playsound(1, SND_CLICK, 1);
	  }
	else if (key == SDLK_ESCAPE)
	  {
	    /* Go back: */
	
	    which = -1;
	    done = 1;
	    playsound(1, SND_CLICK, 1);
	  }
	  else if (key == SDLK_d &&
		   (event.key.keysym.mod & KMOD_CTRL) &&
		   d_places[which] != PLACE_STARTERS_DIR &&
		   !noshortcuts)
	    {
	      /* Delete! */
	  
	      want_erase = 1;
	    }
	}
     else if (event.type == SDL_MOUSEBUTTONDOWN &&
              event.button.button >= 1)
       {
		  if (event.button.x >= 96 && event.button.x < WINDOW_WIDTH - 96 &&
		      event.button.y >= 24 &&
		      event.button.y < (48 * 7 + 40 + HEIGHTOFFSET - 48))
		    {
		      /* Picked an icon! */
		  
		      which = ((event.button.x - 96) / (THUMB_W) +
			       (((event.button.y - 24) / THUMB_H) * 4)) + cur;
		  
		      if (which < num_files)
			{
			  playsound(1, SND_BLEEP, 1);
			  update_list = 1;
		      
		      
			  if (which == last_click_which &&
			      SDL_GetTicks() < last_click_time + 1000 &&
			      event.button.button == last_click_button)
			    {
			      /* Double-click! */
			  
			      done = 1;
			    }
		      
			  last_click_which = which;
			  last_click_time = SDL_GetTicks();
			  last_click_button = event.button.button;
			}
		    }
		  else if (event.button.x >= (WINDOW_WIDTH - img_scroll_up->w) / 2 &&
			   event.button.x <= (WINDOW_WIDTH + img_scroll_up->w) / 2)
		    {
		      if (event.button.y < 24)
			{
			  /* Up scroll button: */
		      
			  if (cur > 0)
			    {
			      cur = cur - 4;
			      update_list = 1;
			      playsound(1, SND_SCROLL, 1);

			      if (cur == 0)
				do_setcursor(cursor_arrow);
			    }
		      
			  if (which > cur + 16)
			    which = which - 4;
			}
		      else if (event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET - 48) &&
			       event.button.y < (48 * 7 + 40 + HEIGHTOFFSET - 24))
			{
			  /* Down scroll button: */
		      
			  if (cur < num_files - 16)
			    {
			      cur = cur + 4;
			      update_list = 1;
			      playsound(1, SND_SCROLL, 1);

			      if (cur >= num_files - 16)
				do_setcursor(cursor_arrow);
			    }
		      
			  if (which < cur)
			    which = which + 4;
			}
		    }
		  else if (event.button.x >= 96 && event.button.x < 96 + 48 &&
			   event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
			   event.button.y < (48 * 7 + 40 + HEIGHTOFFSET))
		    {
		      /* Open */
		  
		      done = 1;
		      playsound(1, SND_CLICK, 1);
		    }
		  else if (event.button.x >= (WINDOW_WIDTH - 96 - 48) &&
			   event.button.x < (WINDOW_WIDTH - 96) &&
			   event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
			   event.button.y < (48 * 7 + 40 + HEIGHTOFFSET))
		    {
		      /* Back */
		  
		      which = -1;
		      done = 1;
		      playsound(1, SND_CLICK, 1);
		    }
		  else if (event.button.x >= (WINDOW_WIDTH - 96 - 48 - 48) &&
			   event.button.x < (WINDOW_WIDTH - 48 - 96) &&
			   event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
			   event.button.y < (48 * 7 + 40 + HEIGHTOFFSET) &&
			   d_places[which] != PLACE_STARTERS_DIR)
		    {
		      /* Erase */
		  
             want_erase = 1;
           }
       }
     else if (event.type == SDL_MOUSEMOTION)
		{
		  /* Deal with mouse pointer shape! */

		  if (event.button.y < 24 &&
		      event.button.x >= (WINDOW_WIDTH - img_scroll_up->w) / 2 &&
		      event.button.x <= (WINDOW_WIDTH + img_scroll_up->w) / 2 &&
		      cur > 0)
		    {
		      /* Scroll up button: */

		      do_setcursor(cursor_up);
		    }
		  else if (event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET - 48) &&
			   event.button.y < (48 * 7 + 40 + HEIGHTOFFSET - 24) &&
			   event.button.x >= (WINDOW_WIDTH - img_scroll_up->w) / 2 &&
			   event.button.x <= (WINDOW_WIDTH + img_scroll_up->w) / 2 &&
			   cur < num_files - 16)
		    {
		      /* Scroll down button: */

		      do_setcursor(cursor_down);
		    }
		  else if (((event.button.x >= 96 && event.button.x < 96 + 48) ||
			    (event.button.x >= (WINDOW_WIDTH - 96 - 48) &&
			     event.button.x < (WINDOW_WIDTH - 96)) ||
			    (event.button.x >= (WINDOW_WIDTH - 96 - 48 - 48) &&
			     event.button.x < (WINDOW_WIDTH - 48 - 96) &&
			     d_places[which] != PLACE_STARTERS_DIR)) &&
			   event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
			   event.button.y < (48 * 7 + 40 + HEIGHTOFFSET))
		    {
		      /* One of the command buttons: */

		      do_setcursor(cursor_hand);
		    }
		  else if (event.button.x >= 96 && event.button.x < WINDOW_WIDTH - 96 &&
			   event.button.y > 24 &&
			   event.button.y < (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
			   ((((event.button.x - 96) / (THUMB_W) +
			      (((event.button.y - 24) / THUMB_H) * 4)) +
			     cur) < num_files))
		    {
		      /* One of the thumbnails: */
	
		      do_setcursor(cursor_hand);
		    }
		  else
		    {
		      /* Unclickable... */

		      do_setcursor(cursor_arrow);
		    }
		}

	  
	      if (want_erase)
		{
		  want_erase = 0;
	      
		  if (do_prompt_image_snd(PROMPT_ERASE_TXT,
					  PROMPT_ERASE_YES, PROMPT_ERASE_NO,
					  thumbs[which],
					  img_popup_arrow,
					  img_trash,
					  SND_AREYOUSURE))
		    {
		      snprintf(fname, sizeof(fname), "saved/%s%s",
			       d_names[which], d_exts[which]);
		  
		      rfname = get_fname(fname);
		      debug(rfname);
		  
		      if (unlink(rfname) == 0)
			{
			  update_list = 1;

		      
			  /* Delete the thumbnail, too: */
		      
			  snprintf(fname, sizeof(fname),
				   "saved/.thumbs/%s-t.png",
				   d_names[which]);
		      
			  free(rfname);
			  rfname = get_fname(fname);
			  debug(rfname);
			  
			  unlink(rfname);


			  /* Try deleting old-style thumbnail, too: */
		      
			  unlink(rfname);
			  snprintf(fname, sizeof(fname), "saved/%s-t.png",
				   d_names[which]);
		      
			  free(rfname);
			  rfname = get_fname(fname);
			  debug(rfname);
		      
			  unlink(rfname);
		      
		      
			  /* Delete .dat file, if any: */
		      
			  unlink(rfname);
			  snprintf(fname, sizeof(fname), "saved/%s.dat",
				   d_names[which]);
		      
			  free(rfname);
			  rfname = get_fname(fname);
			  debug(rfname);
		      
			  unlink(rfname);
		      
		      
			  /* Move all other files up a notch: */
		      
			  free(d_names[which]);
			  free(d_exts[which]);
			  free_surface(&thumbs[which]);
			  
			  thumbs[which] = NULL;
		      
			  for (i = which; i < num_files - 1; i++)
			    {
			      d_names[i] = d_names[i + 1];
			      d_exts[i] = d_exts[i + 1];
			      thumbs[i] = thumbs[i + 1];
			      d_places[i] = d_places[i + 1];
			    }
		      
			  num_files--;
		      
		      
			  /* Make sure the cursor doesn't go off the end! */
		      
			  if (which >= num_files)
			    which = num_files - 1;


			  /* Scroll up if the cursor goes off top of screen! */

			  if (which < cur && cur >= 4)
			    {
			      cur = cur - 4;
			      update_list = 1;
			    }
		      
		      
			  /* No files to open now? */
		      
			  if (which < 0)
			    {
			      do_prompt_snd(PROMPT_OPEN_NOFILES_TXT,
					    PROMPT_OPEN_NOFILES_YES, "",
					    SND_YOUCANNOT);
			      done = 1;
			    }
			}
		      else
			{
			  perror(rfname);
		      
			  do_prompt_snd("CAN'T", "OK", "", SND_YOUCANNOT);
			  update_list = 1;
			}
		  
		      free(rfname);
		    }
		  else
		    {
		      update_list = 1;
		    }
		}
	  
	    }
	  while (!done);
      
      
	  /* Load the chosen picture: */
      
	  if (which != -1)
	    {
	      /* Save old one first? */
	  
	      if (!been_saved && !disable_save)
		{
		  if (do_prompt_image_snd(PROMPT_OPEN_SAVE_TXT,
					  PROMPT_OPEN_SAVE_YES,
					  PROMPT_OPEN_SAVE_NO,
					  img_tools[TOOL_SAVE], NULL, NULL,
					  SND_AREYOUSURE))
		    {
		      do_save();
		    }
		}
	   

	      /* Figure out filename: */

	      snprintf(fname, sizeof(fname), "%s/%s%s",
		       dirname[d_places[which]],
		       d_names[which], d_exts[which]);

#ifdef SAVE_AS_BMP
	      img = SDL_LoadBMP(fname);
#else
	      img = IMG_Load(fname);
#endif
	  
	      if (img == NULL)
		{
		  fprintf(stderr,
			  "\nWarning: Couldn't load the saved image!\n"
			  "%s\n"
			  "The Simple DirectMedia Layer error that occurred "
			  "was:\n"
			  "%s\n\n", fname, SDL_GetError());
	      
		  do_prompt(PROMPT_OPEN_UNOPENABLE_TXT,
			    PROMPT_OPEN_UNOPENABLE_YES, "");
		}
	      else
		{
		  free_surface(&img_starter);
		  free_surface(&img_starter_bkgd);
		  starter_mirrored = 0;
		  starter_flipped = 0;

		  autoscale_copy_smear_free(img,canvas,SDL_BlitSurface);

		  cur_undo = 0;
		  oldest_undo = 0;
		  newest_undo = 0;

		  if (d_places[which] == PLACE_SAVED_DIR)
		  {
		    /* Saved image: */
			  
		    been_saved = 1;
	  
		    strcpy(file_id, d_names[which]);
		    starter_id[0] = '\0';

		    
		    /* See if this saved image was based on a 'starter' */

		    load_starter_id(d_names[which]);

		    if (starter_id[0] != '\0')
		    {
		      load_starter(starter_id);
		      
                      if (starter_mirrored)
                        mirror_starter();
         
		      if (starter_flipped)
			flip_starter();
		    }
		  }
		  else
		  {
		    /* Immutable 'starter' image;
		       we'll need to save a new image when saving...: */

		    been_saved = 1;

      		    file_id[0] = '\0';
		    strcpy(starter_id, d_names[which]);
		    load_starter(starter_id);

		    SDL_FillRect(canvas, NULL, SDL_MapRGB(canvas->format, 255, 255, 255));
		    SDL_BlitSurface(img_starter_bkgd, NULL, canvas, NULL);
		    SDL_BlitSurface(img_starter, NULL, canvas, NULL);
		  }
		  
		  
		  reset_avail_tools();
	      
		  tool_avail_bak[TOOL_UNDO] = 0;
		  tool_avail_bak[TOOL_REDO] = 0;
		}
	    }
  
  
	  update_canvas(0, 0, WINDOW_WIDTH - 96 - 96, 48 * 7 + 40 + HEIGHTOFFSET);
	}


      /* Clean up: */

      free_surface_array(thumbs, num_files);

      free(thumbs);
  
      for (i = 0; i < num_files; i++)
	{
	  free(d_names[i]);
	  free(d_exts[i]);
	}

      for (i = 0; i < NUM_PLACES_TO_LOOK; i++)
	free(dirname[i]);

      free(d_names);
      free(d_exts);
      free(d_places);
}


/* Let sound effects (e.g., "Save" sfx) play out before quitting... */

static void wait_for_sfx(void)
{
#ifndef NOSOUND
  if (use_sound)
    {
      while (Mix_Playing(-1))
	SDL_Delay(10);
    }
#endif
}


////////////////////////////////////////////////////////////
// stamp outline
#ifndef LOW_QUALITY_STAMP_OUTLINE
/* XOR-based outline of rubber stamp shapes
   (unused if LOW_QUALITY_STAMP_OUTLINE is #defined) */

#if 1
#define STIPLE_W 5
#define STIPLE_H 5
static char stiple[] =
"84210"
"10842"
"42108"
"08421"
"21084"
;
#endif

#if 0
#define STIPLE_W 4
#define STIPLE_H 4
static char stiple[] =
"8000"
"0800"
"0008"
"0080"
;
#endif

#if 0
#define STIPLE_W 12
#define STIPLE_H 12
static char stiple[] =
"808808000000"
"800000080880"
"008088080000"
"808000000808"
"000080880800"
"088080000008"
"000000808808"
"080880800000"
"080000008088"
"000808808000"
"880800000080"
"000008088080"
;
#endif

static unsigned char *stamp_outline_data;
static int stamp_outline_w, stamp_outline_h;

static void update_stamp_xor(void)
{
  int xx, yy, rx, ry;
  Uint8 dummy;
  SDL_Surface * src;
  Uint32 (*getpixel)(SDL_Surface *, int, int);
  unsigned char *alphabits;
  int new_w;
  int new_h;
  unsigned char *outline;
  unsigned char *old_outline_data;

  src = active_stamp;

  // start by scaling
  src = thumbnail(src, CUR_STAMP_W, CUR_STAMP_H, 0);

  getpixel = getpixels[src->format->BytesPerPixel];
  alphabits = calloc(src->w+4, src->h+4);

  SDL_LockSurface(src);
  for (yy = 0; yy < src->h; yy++)
    {
      ry = yy;
      for (xx = 0; xx < src->w; xx++)
	{
	  rx = xx;
	  SDL_GetRGBA(getpixel(src, rx, ry),
		      src->format, &dummy, &dummy, &dummy, alphabits + xx+2 + (yy+2)*(src->w+4));
	}
    }
  SDL_UnlockSurface(src);

  new_w = src->w+4;
  new_h = src->h+4;
  SDL_FreeSurface(src);
  outline   = calloc(new_w, new_h);

  for (yy = 1; yy < new_h-1; yy++)
    {
      for (xx = 1; xx < new_w-1; xx++)
	{
          unsigned char above = 0;
          unsigned char below = 0xff;
          unsigned char tmp;

	  tmp = alphabits[(xx-1) + (yy-1)*new_w];
	  above |= tmp;
	  below &= tmp;
	  tmp = alphabits[(xx+1) + (yy-1)*new_w];
	  above |= tmp;
	  below &= tmp;

	  tmp = alphabits[(xx+0) + (yy-1)*new_w];
	  above |= tmp;
	  below &= tmp;
	  tmp = alphabits[(xx+0) + (yy+0)*new_w];
	  above |= tmp;
	  below &= tmp;
	  tmp = alphabits[(xx+1) + (yy+0)*new_w];
	  above |= tmp;
	  below &= tmp;
	  tmp = alphabits[(xx-1) + (yy+0)*new_w];
	  above |= tmp;
	  below &= tmp;
	  tmp = alphabits[(xx+0) + (yy+1)*new_w];
	  above |= tmp;
	  below &= tmp;

	  tmp = alphabits[(xx-1) + (yy+1)*new_w];
	  above |= tmp;
	  below &= tmp;
	  tmp = alphabits[(xx+1) + (yy+1)*new_w];
	  above |= tmp;
	  below &= tmp;

	  outline[xx + yy*new_w] = (above^below)>>7;
	}
    }

  old_outline_data = stamp_outline_data;
  stamp_outline_data = outline;
  stamp_outline_w = new_w;
  stamp_outline_h = new_h;
  if (old_outline_data)
    free(old_outline_data);
  free(alphabits);
}

static void stamp_xor(int x, int y)
{
  int xx, yy, sx, sy;

  SDL_LockSurface(screen);
  for (yy = 0; yy < stamp_outline_h; yy++)
    {
      for (xx = 0; xx < stamp_outline_w; xx++)
	{
          if(!stamp_outline_data[xx + yy*stamp_outline_w])
            continue;
          sx = x + xx - stamp_outline_w/2;
          sy = y + yy - stamp_outline_h/2;
          if (stiple[sx%STIPLE_W + sy%STIPLE_H * STIPLE_W] != '8')
            continue;
          xorpixel(sx, sy);
	}
    }
  SDL_UnlockSurface(screen);
}

#endif
///////////////////////////////////////////////////

static void rgbtohsv(Uint8 r8, Uint8 g8, Uint8 b8, float *h, float *s, float *v)
{
  float rgb_min, rgb_max, delta, r, g, b;
  
  r = (r8 / 255.0);
  g = (g8 / 255.0);
  b = (b8 / 255.0);
  
  rgb_min = min(r, min(g, b));
  rgb_max = max(r, max(g, b));
  *v = rgb_max;
  
  delta = rgb_max - rgb_min;

  if (rgb_max == 0)
    {
      /* Black */
      
      *s = 0;
      *h = -1;
    }
  else
    {
      *s = delta / rgb_max;
      
      if (r == rgb_max)
	*h = (g - b) / delta;
      else if (g == rgb_max)
	*h = 2 + (b - r) / delta;     /* between cyan & yellow */
      else
	*h = 4 + (r - g) / delta;     /* between magenta & cyan */
      
      *h = (*h * 60);                 /* degrees */
      
      if (*h < 0)
	*h = (*h + 360);
    }
}


static void hsvtorgb(float h, float s, float v, Uint8 *r8, Uint8 *g8, Uint8 *b8)
{
  int i;
  float f, p, q, t, r, g, b;
  
  if (s == 0)
    {
      /* Achromatic (grey) */
      
      r = v;
      g = v;
      b = v;
    }
  else
    {
      h = h / 60;
      i = floor(h);
      f = h - i;
      p = v * (1 - s);
      q = v * (1 - s * f);
      t = v * (1 - s * (1 - f));
      
      if (i == 0)
	{
	  r = v;
	  g = t;
	  b = p;
	}
      else if (i == 1)
	{
	  r = q;
	  g = v;
	  b = p;
	}
      else if (i == 2)
	{
	  r = p;
	  g = v;
	  b = t;
	}
      else if (i == 3)
	{
	  r = p;
	  g = q;
	  b = v;
	}
      else if (i == 4)
	{
	  r = t;
	  g = p;
	  b = v;
	}
      else
	{
	  r = v;
	  g = p;
	  b = q;
	}
    }

  
  *r8 = (Uint8) (r * 255);
  *g8 = (Uint8) (g * 255);
  *b8 = (Uint8) (b * 255);
}


static void do_print(void)
{
#if !defined(WIN32) && !defined(__BEOS__) && !defined(__APPLE__)
  char * pcmd;
  FILE * pi;

  /* Linux, Unix, etc. */
  
  if (want_alt_printcommand && !fullscreen)
    pcmd = (char *) altprintcommand;
  else
    pcmd = (char *) printcommand;

  pi = popen(pcmd, "w");

  if (pi == NULL)
    {
      perror(pcmd);
    }
  else
    {
#ifdef PRINTMETHOD_PNG_PNM_PS
      if (do_png_save(pi, pcmd, canvas))
	do_prompt_snd(PROMPT_PRINT_TXT, PROMPT_PRINT_YES, "", SND_TUXOK);
#elif defined(PRINTMETHOD_PNM_PS)
      // nothing here
#elif defined(PRINTMETHOD_PS)
      if (do_ps_save(pi, pcmd, canvas))
	do_prompt_snd(PROMPT_PRINT_TXT, PROMPT_PRINT_YES, "", SND_TUXOK);
#else
#error No print method defined!
#endif
    }
#else
#ifdef WIN32
  /* Win32 */

  char  f[512];
  int   show = (want_alt_printcommand && !fullscreen);

  snprintf(f, sizeof(f), "%s/%s", savedir, "print.cfg");

  {
    const char   *error = SurfacePrint(canvas, use_print_config?f:NULL, show);

    if ( error ) fprintf(stderr, "%s\n", error);
  }
#elif defined(__BEOS__)
  /* BeOS */
  
  SurfacePrint(canvas);
#elif defined(__APPLE__)
  /* Mac OS X */

  int show = (want_alt_printcommand && !fullscreen);

  const char* error = SurfacePrint (canvas, show);

  if (error)
    fprintf (stderr, "Cannot print: %s\n", error);
  else
    do_prompt_snd(PROMPT_PRINT_TXT, PROMPT_PRINT_YES, "", SND_TUXOK);
#endif
  
#endif
}


static void do_render_cur_text(int do_blit)
{
  int w, h;
  SDL_Color color = {color_hexes[cur_color][0],
	             color_hexes[cur_color][1],
      	             color_hexes[cur_color][2],
      	             0};
  SDL_Surface * tmp_surf;
  SDL_Rect dest, src;
  char * str;
    

  /* Keep cursor on the screen! */

  if (cursor_y > ((48 * 7 + 40 + HEIGHTOFFSET) -
		  TTF_FontHeight(getfonthandle(cur_font))))
    {
      cursor_y = ((48 * 7 + 40 + HEIGHTOFFSET) -
		  TTF_FontHeight(getfonthandle(cur_font)));
    }
  
  
  /* Render the text: */

  if (texttool_len > 0)
    {
      str = uppercase(texttool_str);
    
      tmp_surf = render_text(getfonthandle(cur_font), str, color);

      w = tmp_surf->w;
      h = tmp_surf->h;
    
      cursor_textwidth = w;

      free(str);
    }
  else
    {
      /* FIXME: Do something different! */

      update_canvas(0, 0, WINDOW_WIDTH - 96, (48 * 7) + 40 + HEIGHTOFFSET);
      cursor_textwidth = 0;
      return;
    }


  if (!do_blit)
    {
      /* FIXME: Only delete what's changed! */

      update_canvas(0, 0, WINDOW_WIDTH - 96, (48 * 7) + 40 + HEIGHTOFFSET);


      /* Draw outline around text: */

      dest.x = cursor_x - 2 + 96;
      dest.y = cursor_y - 2;
      dest.w = w + 4;
      dest.h = h + 4;
    
      if (dest.x + dest.w > WINDOW_WIDTH - 96)
	dest.w = WINDOW_WIDTH - 96 - dest.x;
      if (dest.y + dest.h > (48 * 7 + 40 + HEIGHTOFFSET))
	dest.h = (48 * 7 + 40 + HEIGHTOFFSET) - dest.y;

      SDL_FillRect(screen, &dest,
		   SDL_MapRGB(canvas->format, 0, 0, 0));


      /* FIXME: This would be nice if it were alpha-blended: */

      dest.x = cursor_x + 96;
      dest.y = cursor_y;
      dest.w = w;
      dest.h = h;

      if (dest.x + dest.w > WINDOW_WIDTH - 96)
	dest.w = WINDOW_WIDTH - 96 - dest.x;
      if (dest.y + dest.h > (48 * 7 + 40 + HEIGHTOFFSET))
	dest.h = (48 * 7 + 40 + HEIGHTOFFSET) - dest.y;

      if ((color_hexes[cur_color][0] +
	   color_hexes[cur_color][1] +
	   color_hexes[cur_color][2]) >= 384)
	{
	  /* Grey background if blit is white!... */

	  SDL_FillRect(screen, &dest,
		       SDL_MapRGB(canvas->format, 64, 64, 64));
	}
      else
	{
	  /* White background, normally... */

	  SDL_FillRect(screen, &dest,
		       SDL_MapRGB(canvas->format, 255, 255, 255));
	}
    }
  

  /* Draw the text itself! */

  if (tmp_surf != NULL)
    {
      dest.x = cursor_x;
      dest.y = cursor_y;

      src.x = 0;
      src.y = 0;
      src.w = tmp_surf->w;
      src.h = tmp_surf->h;

      if (dest.x + src.w > WINDOW_WIDTH - 96 - 96)
	src.w = WINDOW_WIDTH - 96 - 96 - dest.x;
      if (dest.y + src.h > (48 * 7 + 40 + HEIGHTOFFSET))
	src.h = (48 * 7 + 40 + HEIGHTOFFSET) - dest.y;

      if (do_blit)
	{
	  SDL_BlitSurface(tmp_surf, &src, canvas, &dest);
	  update_canvas(dest.x, dest.y, dest.x + tmp_surf->w, dest.y + tmp_surf->h);
	}
      else
	{
	  dest.x = dest.x + 96;
	  SDL_BlitSurface(tmp_surf, &src, screen, &dest);
	}
    }
  
  
  /* FIXME: Only update what's changed! */
  
  SDL_Flip(screen);


  if (tmp_surf != NULL)
    SDL_FreeSurface(tmp_surf);
}


/* Return string as uppercase if that option is set: */

#ifdef OLD_UPPERCASE_CODE

static char * uppercase(char * str)
{
  char * ustr;
  int i;

  ustr = strdup(str);

  if (only_uppercase)
    {
      for (i = 0; i < strlen(ustr); i++)
	ustr[i] = toupper(ustr[i]);
    }
  
#ifdef DEBUG
  printf(" ORIGINAL: %s\n"
         "UPPERCASE: %s\n\n", str, ustr);
#endif

  return (ustr);
}

#else

static char * uppercase(char * str)
{
  unsigned int i, sz;
  wchar_t * dest;
  char * ustr;

  if (only_uppercase)
  {
    sz = sizeof(wchar_t) * (strlen(str) + 1);

    dest = (wchar_t *) malloc(sz);
    // FIXME: uppercase chars may need extra bytes
    ustr = malloc(strlen(str) + 1);

    if (dest != NULL)
    {
      mbstowcs(dest, str, sz);

      for (i = 0; i < strlen(str); i++)
      {
	dest[i] = towupper(dest[i]);
      }

      wcstombs(ustr, dest, sizeof(char) * (strlen(str) + 1));
      
      free(dest);
    }
  
#ifdef DEBUG
    printf(" ORIGINAL: %s\n"
           "UPPERCASE: %s\n\n", str, ustr);
#endif
  }
  else
  {
    ustr = strdup(str);
  }

  return(ustr);
}

#endif


/* Return string in right-to-left mode, if necessary: */

static char * textdir(const char * const str)
{
  unsigned char * dstr;
  unsigned i, j;
  unsigned char c1, c2, c3, c4;
  
#ifdef DEBUG
  printf("ORIG_DIR: %s\n", str);
#endif
  
  dstr = malloc(strlen(str) + 5);

  if (need_right_to_left)
    {
      dstr[strlen(str)] = '\0';

      for (i = 0; i < strlen(str); i++)
	{
	  j = (strlen(str) - i - 1);
	  
	  c1 = (unsigned char) str[i + 0];
	  c2 = (unsigned char) str[i + 1];
	  c3 = (unsigned char) str[i + 2];
	  c4 = (unsigned char) str[i + 3];
	  
	  if (c1 < 128)  /* 0xxx xxxx - 1 byte */
	    {
	      dstr[j] = str[i];
	    }
	  else if ((c1 & 0xE0) == 0xC0)  /* 110x xxxx - 2 bytes */
	    {
	      dstr[j - 1] = c1;
	      dstr[j - 0] = c2;
	      i = i + 1;
	    }
	  else if ((c1 & 0xF0) == 0xE0)  /* 1110 xxxx - 3 bytes */
	    {
	      dstr[j - 2] = c1;
	      dstr[j - 1] = c2;
	      dstr[j - 0] = c3;
	      i = i + 2;
	    }
	  else  /* 1111 0xxx - 4 bytes */
	    {
	      dstr[j - 3] = c1;
	      dstr[j - 2] = c2;
	      dstr[j - 1] = c3;
	      dstr[j - 0] = c4;
	      i = i + 3;
	    }
	}
    }
  else
    {
      strcpy((char *) dstr, str);
    }

#ifdef DEBUG
  printf("L2R_DIR: %s\n", dstr);
#endif
  
  return ((char *) dstr);
}


/* For flood fill... */

static int colors_close(Uint32 c1, Uint32 c2)
{
#ifdef LOW_QUALITY_FLOOD_FILL
  return (c1 == c2);
#else
  Uint8 r1, g1, b1,
    r2, g2, b2;

  if (c1 == c2)
    {
      /* Get it over with quick, if possible! */

      return 1;
    }
  else
    {
      double r, g, b;
      SDL_GetRGB(c1, canvas->format, &r1, &g1, &b1);
      SDL_GetRGB(c2, canvas->format, &r2, &g2, &b2);

      // use distance in linear RGB space
      r = sRGB_to_linear_table[r1] - sRGB_to_linear_table[r2];
      r *= r;
      g = sRGB_to_linear_table[g1] - sRGB_to_linear_table[g2];
      g *= g;
      b = sRGB_to_linear_table[b1] - sRGB_to_linear_table[b2];
      b *= b;

      // easy to confuse:
      //   dark grey, brown, purple
      //   light grey, tan
      //   red, orange
      return r+g+b < 0.04;
    }
#endif
}


/* Flood fill! */

static void do_flood_fill(int x, int y, Uint32 cur_colr, Uint32 old_colr)
{
  int fillL, fillR, i, in_line;
  static unsigned char prog_anim;
  Uint32 (*getpixel)(SDL_Surface *, int, int) = getpixels[canvas->format->BytesPerPixel];
  void (*putpixel)(SDL_Surface *, int, int, Uint32) = putpixels[canvas->format->BytesPerPixel];


  if (cur_colr == old_colr ||
      colors_close(cur_colr, old_colr))
    return;

  
  fillL = x;
  fillR = x;
  
  prog_anim++;
  if ((prog_anim % 4) == 0)
    {
      show_progress_bar();
      playsound(0, SND_BUBBLE, 0);
    }


  /* Find left side, filling along the way */
  
  in_line = 1;
  
  while (in_line)
    {
      putpixel(canvas, fillL, y, cur_colr);
      fillL--;

      in_line = (fillL < 0) ? 0 : colors_close(getpixel(canvas, fillL, y),
					       old_colr);
    }
  
  fillL++;

  
  /* Find right side, filling along the way */

  in_line = 1;
  while (in_line)
    {
      putpixel(canvas, fillR, y, cur_colr);
      fillR++;
    
      in_line = (fillR >= canvas->w) ? 0 : colors_close(getpixel(canvas,
								 fillR, y),
							old_colr);
    }
  
  fillR--;

  
  /* Search top and bottom */
  
  for (i = fillL; i <= fillR; i++)
    {
      if (y > 0 && colors_close(getpixel(canvas, i, y - 1), old_colr))
	do_flood_fill(i, y - 1, cur_colr, old_colr);

      if (y < canvas->h && colors_close(getpixel(canvas, i, y + 1), old_colr))
	do_flood_fill(i, y + 1, cur_colr, old_colr);
    }
}


/* Scroll Timer */

static Uint32 scrolltimer_callback(Uint32 interval, void *param)
{
  SDL_PushEvent((SDL_Event*)param);

  return interval;
}


/* Controls the Text-Timer - interval == 0 removes the timer */

static void control_drawtext_timer(Uint32 interval, const char * const text)
{
  static int activated = 0;
  static SDL_TimerID TimerID = 0;
  static SDL_Event drawtext_event;

  
  /* Remove old timer if any is running */
  
  if (activated)
    {
      SDL_RemoveTimer(TimerID);
      activated = 0;
      TimerID = 0;
    }

  if (interval == 0)
    return;
  
  drawtext_event.type = SDL_USEREVENT;
  drawtext_event.user.code = USEREVENT_TEXT_UPDATE;
  drawtext_event.user.data1 = (void*) text;

  
  /* Add new timer */
  
  TimerID = SDL_AddTimer(interval, drawtext_callback, (void*) &drawtext_event);
  activated = 1;
}


/* Drawtext Timer */

static Uint32 drawtext_callback(Uint32 interval, void *param)
{
  (void)interval;
  SDL_PushEvent((SDL_Event*)param);

  return 0; /* Remove timer */
}


static void parse_options(FILE * fi)
{
  char str[256];
  
  do
    {
      fgets(str, sizeof(str), fi);

      strip_trailing_whitespace(str);

      if (!feof(fi))
	{
	  debug(str);
      
      
	  /* FIXME: This should be handled better! */
	  /* (e.g., complain on illegal lines, support comments, blanks, etc.) */

	  if (strcmp(str, "fullscreen=yes") == 0)
	    {
	      fullscreen = 1;
	    }
	  else if (strcmp(str, "fullscreen=no") == 0 ||
		   strcmp(str, "windowed=yes") == 0)
	    {
	      fullscreen = 0;
	    }
	  else if (strcmp(str, "nostampcontrols=yes") == 0)
	    {
	      disable_stamp_controls = 1;
	    }
	  else if (strcmp(str, "nostampcontrols=no") == 0 ||
		   strcmp(str, "stampcontrols=yes") == 0)
	    {
	      disable_stamp_controls = 0;
	    }
	  else if (strcmp(str, "mirrorstamps=yes") == 0)
	    {
	      mirrorstamps = 1;
	    }
	  else if (strcmp(str, "mirrorstamps=no") == 0 ||
		   strcmp(str, "dontmirrorstamps=yes") == 0)
	    {
	      mirrorstamps = 0;
	    }
	  else if (strcmp(str, "noshortcuts=yes") == 0)
	    {
	      noshortcuts = 1;
	    }
	  else if (strcmp(str, "noshortcuts=no") == 0 ||
		   strcmp(str, "shortcuts=yes") == 0)
	    {
	      noshortcuts = 0;
	    }
	  else if (!memcmp("windowsize=",str,11))
	    {
	      char *endp1;
	      char *endp2;
	      int w,h;
	      w = strtoul(str+11,  &endp1, 10);
	      h = strtoul(endp1+1, &endp2, 10);
	      // sanity check it
	      if(str+11==endp1 || endp1+1==endp2 || *endp1!='x' || *endp2 || w<500 || h<480 || h>w*3 || w>h*4)
		{
		  // Oddly, config files have no error checking.
		  //show_usage(stderr, (char *) getfilename(argv[0]));
		  //exit(1);
		}
	      else
	        {
		  WINDOW_WIDTH  = w;
		  WINDOW_HEIGHT = h;
		}
	    }
	  // to handle old config files
	  else if (strcmp(str, "800x600=yes") == 0 ||
		   strcmp(str, "windowsize=800x600") == 0)
	    {
	      WINDOW_WIDTH = 800;
	      WINDOW_HEIGHT = 600;
	    }
	  // also for old config files
	  else if (strcmp(str, "800x600=no") == 0 ||
		   strcmp(str, "640x480=yes") == 0 ||
		   strcmp(str, "windowsize=640x480") == 0)
	    {
	      WINDOW_WIDTH = 640;
	      WINDOW_HEIGHT = 480;
	    }
	  else if (strcmp(str, "nooutlines=yes") == 0)
	    {
	      dont_do_xor = 1;
	    }
	  else if (strcmp(str, "nooutlines=no") == 0 ||
		   strcmp(str, "outlines=yes") == 0)
	    {
	      dont_do_xor = 0;
	    }
	  else if (strcmp(str, "keyboard=yes") == 0)
	    {
	      keymouse = 1;
	    }
	  else if (strcmp(str, "keyboard=no") == 0 ||
		   strcmp(str, "mouse=yes") == 0)
	    {
	      keymouse = 0;
	    }
	  else if (strcmp(str, "nowheelmouse=yes") == 0)
	    {
	      wheely = 0;
	    }
	  else if (strcmp(str, "nowheelmouse=no") == 0 ||
		   strcmp(str, "wheelmouse=yes") == 0)
	    {
	      wheely = 1;
	    }
	  else if (strcmp(str, "grab=yes") == 0)
	    {
	      grab_input = 1;
	    }
	  else if (strcmp(str, "grab=no") == 0 ||
		   strcmp(str, "nograb=yes") == 0)
	    {
	      grab_input = 0;
	    }
	  else if (strcmp(str, "nofancycursors=yes") == 0)
	    {
	      no_fancy_cursors = 1;
	    }
	  else if (strcmp(str, "nofancycursors=no") == 0 ||
		   strcmp(str, "fancycursors=yes") == 0)
	    {
	      no_fancy_cursors = 0;
	    }
	  else if (strcmp(str, "uppercase=yes") == 0)
	    {
	      only_uppercase = 1;
	    }
	  else if (strcmp(str, "uppercase=no") == 0 ||
		   strcmp(str, "mixedcase=yes") == 0)
	    {
	      only_uppercase = 0;
	    }
	  else if (strcmp(str, "noquit=yes") == 0)
	    {
	      disable_quit = 1;
	    }
	  else if (strcmp(str, "noquit=no") == 0 ||
		   strcmp(str, "quit=yes") == 0)
	    {
	      disable_quit = 0;
	    }
	  else if (strcmp(str, "nosave=yes") == 0)
	    {
	      disable_save = 1;
	    }
	  else if (strcmp(str, "nosave=no") == 0 ||
		   strcmp(str, "save=yes") == 0)
	    {
	      disable_save = 0;
	    }
	  else if (strcmp(str, "noprint=yes") == 0)
	    {
	      disable_print = 1;
	    }
	  else if (strcmp(str, "noprint=no") == 0 ||
		   strcmp(str, "print=yes") == 0)
	    {
	      disable_print = 0;
	    }
	  else if (strcmp(str, "nostamps=yes") == 0)
	    {
	      dont_load_stamps = 1;
	    }
	  else if (strcmp(str, "nostamps=no") == 0 ||
		   strcmp(str, "stamps=yes") == 0)
	    {
	      dont_load_stamps = 0;
	    }
	  else if (strcmp(str, "nosysfonts=yes") == 0 ||
	           strcmp(str, "sysfonts=no") == 0)
	    {
	      no_system_fonts = 1;
	    }
	  else if (strcmp(str, "nosysfonts=no") == 0 ||
		   strcmp(str, "sysfonts=yes") == 0)
	    {
	      no_system_fonts = 0;
	    }
	  else if (strcmp(str, "nobuttondistinction=yes") == 0)
	    {
	      no_button_distinction= 1;
	    }
	  else if (strcmp(str, "nobuttondistinction=no") == 0 ||
		   strcmp(str, "buttondistinction=yes") == 0)
	    {
	      no_button_distinction= 0;
	    }
	  else if (strcmp(str, "nosound=yes") == 0)
	    {
	      use_sound = 0;
	    }
	  else if (strcmp(str, "nosound=no") == 0 ||
		   strcmp(str, "sound=yes") == 0)
	    {
	      use_sound = 1;
	    }
	  else if (strcmp(str, "simpleshapes=yes") == 0)
	    {
	      simple_shapes = 1;
	    }
	  else if (strcmp(str, "simpleshapes=no") == 0 ||
		   strcmp(str, "complexshapes=yes") == 0)
	    {
	      simple_shapes = 1;
	    }
	  // Should "locale=" be here as well???
	  // Comments welcome ... bill@newbreedsoftware.com
	  else if (strstr(str, "lang=") == str)
	    {
	      set_langstr(str + 5);
	    }
	  else if (strstr(str, "printdelay=") == str)
	    {
	      sscanf(str + 11, "%d", &print_delay);
#ifdef DEBUG
	      printf("Print delay set to %d seconds\n", print_delay);
#endif
	    }
	  else if (strcmp(str, "printcfg=yes") == 0)
	    {
#ifndef WIN32
	      fprintf(stderr, "Note: printcfg option only applies to Windows!\n");
#endif
	      use_print_config = 1;
	    }
	  else if (strcmp(str, "printcfg=no") == 0 ||
		   strcmp(str, "noprintcfg=yes") == 0)
	    {
#ifndef WIN32
	      fprintf(stderr, "Note: printcfg option only applies to Windows!\n");
#endif
	      use_print_config = 0;
	    }
	  else if (strstr(str, "printcommand=") == str)
	    {
	      printcommand = strdup(str + 13);
	    }
	  else if (strstr(str, "altprintcommand=") == str)
	    {
	      altprintcommand = strdup(str + 16);
	    }
	  else if (strcmp(str, "saveover=yes") == 0)
	    {
	      promptless_save = SAVE_OVER_ALWAYS;
	    }
	  else if (strcmp(str, "saveover=ask") == 0)
	    {
	      /* (Default) */

	      promptless_save = SAVE_OVER_PROMPT;
	    }
	  else if (strcmp(str, "saveover=new") == 0)
	    {
	      promptless_save = SAVE_OVER_NO;
	    }
	  else if (strcmp(str, "altprint=always") == 0)
	    {
	      alt_print_command_default = ALTPRINT_ALWAYS;
	    }
	  else if (strcmp(str, "altprint=mod") == 0)
	    {
	      /* (Default) */

	      alt_print_command_default = ALTPRINT_MOD;
	    }
	  else if (strcmp(str, "altprint=never") == 0)
	    {
	      alt_print_command_default = ALTPRINT_NEVER;
	    }
	  else if (strstr(str, "savedir=") == str)
	    {
	      savedir = strdup(str + 8);
	      remove_slash(savedir);

#ifdef DEBUG
	      printf("savedir set to: %s\n", savedir);
#endif
	    }
	}
    }
  while (!feof(fi));
}


#ifdef DEBUG
static char * debug_gettext(const char * str)
{
  if (strcmp(str, dgettext(NULL, str)) == 0)
    {
      printf("NOTRANS: %s\n", str);
      printf("..TRANS: %s\n", dgettext(NULL, str));
      fflush(stdout);
    }

  return(dgettext(NULL, str));
}
#endif


static void do_setcursor(SDL_Cursor * c)
{
  if (!no_fancy_cursors)
    SDL_SetCursor(c);
}


static const char * great_str(void)
{
  return(great_strs[rand() % (sizeof(great_strs) / sizeof(char *))]);
}


#ifdef DEBUG
static int charsize(char c)
{
  Uint16 str[2];
  int w, h;

  str[0] = c;
  str[1] = '\0';

  TTF_SizeUNICODE(getfonthandle(cur_font), str, &w, &h);

  return w;
}
#endif

static void draw_image_title(int t, SDL_Rect dest)
{
  SDL_BlitSurface(img_title_on, NULL, screen, &dest);
  
  dest.x += (dest.w - img_title_names[t]->w) / 2;
  dest.y += (dest.h - img_title_names[t]->h) / 2;
  SDL_BlitSurface(img_title_names[t], NULL, screen, &dest);
}



/* Handle keyboard events to control the mouse: */

static void handle_keymouse(SDLKey key, Uint8 updown)
{
  SDL_Event event;

  if (keymouse)
    {
      if (key == SDLK_LEFT)
	mousekey_left = updown;
      else if (key == SDLK_RIGHT)
	mousekey_right = updown;
      else if (key == SDLK_UP)
	mousekey_up = updown;
      else if (key == SDLK_DOWN)
	mousekey_down = updown;
      else if (key == SDLK_SPACE)
	{
	  if (updown == SDL_KEYDOWN)
	    event.type = SDL_MOUSEBUTTONDOWN;
	  else
	    event.type = SDL_MOUSEBUTTONUP;
      
	  event.button.x = mouse_x;
	  event.button.y = mouse_y;
	  event.button.button = 1;

	  SDL_PushEvent(&event);
	}

      if (mousekey_up == SDL_KEYDOWN && mouse_y > 0)
	mouse_y = mouse_y - 8;
      else if (mousekey_down == SDL_KEYDOWN && mouse_y < WINDOW_HEIGHT - 1)
	mouse_y = mouse_y + 8;
    
      if (mousekey_left == SDL_KEYDOWN && mouse_x > 0)
	mouse_x = mouse_x - 8;
      if (mousekey_right == SDL_KEYDOWN && mouse_x < WINDOW_WIDTH - 1)
	mouse_x = mouse_x + 8;

      SDL_WarpMouse(mouse_x, mouse_y);
    }
}


/* Unblank screen in fullscreen mode, if needed: */

static void handle_active( SDL_Event *event )
{
  if (event->active.state & SDL_APPACTIVE)
    {
      if (event->active.gain == 1 )
	{
	  if ( fullscreen )
	    SDL_Flip(screen);
	}
    }
}


/* removes a single '\' or '/' from end of path */

static char *remove_slash( char *path )
{
  int len = strlen(path);

  if (!len)
    return path;

  if (path[len-1] == '/' || path[len-1] == '\\')
    path[len-1] = 0;

  return path;
}


/* For right-to-left languages, when word-wrapping, we need to
   make sure the text doesn't end up going from bottom-to-top, too! */

static void anti_carriage_return(int left, int right, int cur_top, int new_top,
		          int cur_bot, int line_width)
{
  SDL_Rect src, dest;


  /* Move current set of text down one line (and right-justify it!): */
  
  src.x = left;
  src.y = cur_top;
  src.w = line_width;
  src.h = cur_bot - cur_top;

  dest.x = right - line_width;
  dest.y = new_top;

  SDL_BlitSurface(screen, &src, screen, &dest);


  /* Clear the top line for new text: */

  dest.x = left;
  dest.y = cur_top;
  dest.w = right - left;
  dest.h = new_top - cur_top;
  
  SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 255, 255, 255));
}


static int mySDL_WaitEvent(SDL_Event *event)
{
  int ret;

  
  if (playing)
    {
      if (!feof(demofi))
	{
	  ret = 1;
	  fread(event, sizeof(SDL_Event), 1, demofi);
	}
      else
	{
	  /* All done!  Back to normal! */

	  printf("(Done playing playback file '%s')\n", playfile);

	  ret = 0;
	  playing = 0;
	}
    }
  else
    {
      ret = SDL_WaitEvent(event);

      if (recording)
	{
	  fwrite(event, sizeof(SDL_Event), 1, demofi);
	}
    }

  return ret;
}


static int mySDL_PollEvent(SDL_Event *event)
{
  int ret;

  if (playing)
    {
      if (!feof(demofi))
	{
	  ret = 1;
	  fread(event, sizeof(SDL_Event), 1, demofi);
	}
      else
	{
	  /* All done!  Back to normal! */

	  printf("(Done playing playback file '%s')\n", playfile);

	  ret = 0;
	  playing = 0;
	}
    }
  else
    {
      ret = SDL_PollEvent(event);

      if (recording && ret > 0)
	{
	  fwrite(event, sizeof(SDL_Event), 1, demofi);
	}
    }

  return ret;
}


static SDL_Surface * duplicate_surface(SDL_Surface * orig)
{
  /*
  Uint32 amask;

  amask = ~(orig->format->Rmask |
	    orig->format->Gmask |
	    orig->format->Bmask);
  
  return(SDL_CreateRGBSurface(SDL_SWSURFACE,
			      orig->w, orig->h,
			      orig->format->BitsPerPixel,
			      orig->format->Rmask,
			      orig->format->Gmask,
			      orig->format->Bmask,
			      amask));
  */

  return(SDL_DisplayFormatAlpha(orig));
}

static void mirror_starter(void)
{
  SDL_Surface * orig;
  int x;
  SDL_Rect src, dest;

  
  /* Mirror overlay: */

  orig = img_starter;
  img_starter = duplicate_surface(orig);

  if (img_starter != NULL)
  {
    for (x = 0; x < orig->w; x++)
    {
      src.x = x;
      src.y = 0;
      src.w = 1;
      src.h = orig->h;

      dest.x = orig->w - x - 1;
      dest.y = 0;

      SDL_BlitSurface(orig, &src, img_starter, &dest);
    }

    SDL_FreeSurface(orig);
  }
  else
  {
    img_starter = orig;
  }


  /* Mirror background: */

  if (img_starter_bkgd != NULL)
  {
    orig = img_starter_bkgd;
    img_starter_bkgd = duplicate_surface(orig);

    if (img_starter_bkgd != NULL)
    {
      for (x = 0; x < orig->w; x++)
      {
        src.x = x;
        src.y = 0;
        src.w = 1;
        src.h = orig->h;

        dest.x = orig->w - x - 1;
        dest.y = 0;

        SDL_BlitSurface(orig, &src, img_starter_bkgd, &dest);
      }

      SDL_FreeSurface(orig);
    }
    else
    {
      img_starter_bkgd = orig;
    }
  }
}


static void flip_starter(void)
{
  SDL_Surface * orig;
  int y;
  SDL_Rect src, dest;

  
  /* Flip overlay: */

  orig = img_starter;
  img_starter = duplicate_surface(orig);

  if (img_starter != NULL)
  {
    for (y = 0; y < orig->h; y++)
    {
      src.x = 0;
      src.y = y;
      src.w = orig->w;
      src.h = 1;

      dest.x = 0;
      dest.y = orig->h - y - 1;

      SDL_BlitSurface(orig, &src, img_starter, &dest);
    }

    SDL_FreeSurface(orig);
  }
  else
  {
    img_starter = orig;
  }


  /* Flip background: */

  if (img_starter_bkgd != NULL)
  {
    orig = img_starter_bkgd;
    img_starter_bkgd = duplicate_surface(orig);

    if (img_starter_bkgd != NULL)
    {
      for (y = 0; y < orig->h; y++)
      {
        src.x = 0;
        src.y = y;
        src.w = orig->w;
        src.h = 1;

        dest.x = 0;
        dest.y = orig->h - y - 1;

        SDL_BlitSurface(orig, &src, img_starter_bkgd, &dest);
      }

      SDL_FreeSurface(orig);
    }
    else
    {
      img_starter_bkgd = orig;
    }
  }
}


int valid_click(Uint8 button)
{
  if (button == 1 ||
      ((button == 2 || button == 3) && no_button_distinction))
    return(1);
  else
    return(0);
}
