/* tuxpaint.c Tux Paint - A simple drawing program for children. Copyright (c) 2004 by Bill Kendrick 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 June 14, 2002 - November 26, 2004 */ #define VER_VERSION "0.9.15" #define VER_DATE "2004-11-26" //#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 /* #define DEBUG */ /* #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 */ /* Use high quality 4x filter when scaling stamps up: */ /* #define USE_HQ4X */ /* Disable fancy cursors in fullscreen mode, to avoid SDL bug: */ #define LARGE_CURSOR_FULLSCREEN_BUG #define HEIGHTOFFSET (((WINDOW_HEIGHT - 480) / 48) * 48) #define TOOLOFFSET (HEIGHTOFFSET / 48 * 2) #define PROMPTOFFSETX (WINDOW_WIDTH - 640) / 2 #define PROMPTOFFSETY (HEIGHTOFFSET / 2) ///////////////////////////////////////////////////////////////////// // 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 }; #define HARD_MIN_STAMP_SIZE 0 // bottom of scaletable #define HARD_MAX_STAMP_SIZE (sizeof scaletable / sizeof scaletable[0] - 1) #define MIN_STAMP_SIZE (state_stamps[cur_stamp]->min) #define MAX_STAMP_SIZE (state_stamps[cur_stamp]->max) #define CAN_USE_HQ4X (scaletable[state_stamps[cur_stamp]->size] == (scaleparams){4,1}) // to scale some offset, in pixels, like the current stamp is scaled #define SCALE_LIKE_STAMP(x) ( ((x) * scaletable[state_stamps[cur_stamp]->size].numer + scaletable[state_stamps[cur_stamp]->size].denom-1) / scaletable[state_stamps[cur_stamp]->size].denom ) // pixel dimensions of the current stamp, as scaled #define CUR_STAMP_W SCALE_LIKE_STAMP(img_stamps[cur_stamp]->w) #define CUR_STAMP_H SCALE_LIKE_STAMP(img_stamps[cur_stamp]->h) /////////////////////////////////////////////////////////////////////////////// // #define MAX_FILES 2048 /* Max. # of files in a dir. to worry about... */ #define REPEAT_SPEED 300 /* Initial repeat speed for scrollbars */ #define CURSOR_BLINK_SPEED 500 /* Initial repeat speed for cursor */ #include #include #include #include #include #include #ifdef USE_HQ4X #include "hqxx.h" #include "hq3x.h" #include "hq4x.h" #endif #include #include #ifndef OLD_UPPERCASE_CODE #include #endif #ifdef WIN32_OLD /* The following are required by libintl.h, so must be defined first: */ #define LC_MESSAGES 1729 #define HAVE_LC_MESSAGES 1 #define ENABLE_NLS 1 #define HAVE_LOCALE_H 1 #define HAVE_GETTEXT 1 #define HAVE_DCGETTEXT 1 #endif #if defined(sun) && defined(__svr4__) /* Solaris needs locale.h */ #endif #include #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.14159265 #endif #include #include #ifndef WIN32 #include #include #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" #endif #else #include "win32_dirent.h" #include "win32_print.h" #include #include /* Enables win32 apps to get a GNU compatible locale string */ extern char* g_win32_getlocale(void); /* Set this to 0 during developement and testing in Visual-Studio Set this to 1 to make the final executable */ #if 1 #define DOC_PREFIX "docs/" #define DATA_PREFIX "data/" #define LOCALEDIR "locale" #else #define DOC_PREFIX "../../docs/" #define DATA_PREFIX "../../data/" #define LOCALEDIR "../../locale" #endif /* 1/0 */ #define mkdir(path,access) _mkdir(path) #define strcasecmp stricmp #define strncasecmp strnicmp #define snprintf _snprintf #define S_ISDIR(i) ((i&_S_IFDIR)!=0) #define alloca _alloca #endif /* WIN32 */ #include #include #include "SDL.h" #ifndef _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" #ifndef _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" #ifndef _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" #ifndef _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 #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 #define THUMB_W ((WINDOW_WIDTH - 96 - 96) / 4) #define THUMB_H (((48 * 7 + 40 + HEIGHTOFFSET) - 72) / 4) #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 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 #endif #ifndef 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 #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 /* 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_EU, /* Basque */ LANG_FI, /* Finnish */ LANG_FR, /* French */ LANG_GL, /* Galician */ 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_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_SK, /* Slovak */ LANG_SL, /* Slovenian */ LANG_SQ, /* Albanian */ LANG_SR, /* Serbian */ LANG_SV, /* Swedish */ LANG_TA, /* Tamil */ LANG_TR, /* Turkish */ 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", "eu", "fi", "fr", "gl", "he", "hi", "hr", "hu", "tlh", "id", "is", "it", "ja", "ko", "lt", "ms", "nb", "nl", "nn", "pl", "pt_br", "pt_pt", "ro", "ru", "sk", "sl", "sq", "sr", "sv", "ta", "tr", "vi", "wa", "zh_cn", "zh_tw" }; /* List of languages which doesn't use the default font: */ static int lang_use_own_font[] = { LANG_EL, LANG_HE, LANG_HI, LANG_JA, LANG_KO, LANG_TA, LANG_ZH_CN, LANG_ZH_TW, -1 }; static int lang_use_right_to_left[] = { LANG_HE, -1 }; typedef struct info_type { double ratio; unsigned tinter; int colorable; int tintable; int mirrorable; int flipable; int tintgray; } info_type; typedef struct state_type { int mirrored; int flipped; unsigned min,size,max; } state_type; enum { SAVE_OVER_PROMPT, SAVE_OVER_ALWAYS, SAVE_OVER_NO }; enum { STARTER_OUTLINE, STARTER_SCENE }; /* Globals: */ static int use_sound, fullscreen, disable_quit, simple_shapes, language, 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, mirrorstamps, disable_stamp_controls, disable_save; static int recording, playing; static char * playfile; static FILE * demofi; static int WINDOW_WIDTH, WINDOW_HEIGHT; static const char * printcommand; static int prog_bar_ctr; static SDL_Surface * screen; static SDL_Surface * canvas; static SDL_Surface * img_starter, * img_starter_bkgd; static int starter_mirrored, starter_flipped; 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_progress; static SDL_Surface * img_btn_up, * img_btn_down, * img_btn_off; static SDL_Surface * img_yes, * img_no; static SDL_Surface * img_open, * img_erase, * img_back; 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_paintcan; static SDL_Surface * img_grow, * img_shrink; static SDL_Surface * img_sparkles; 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]; #define MAX_STAMPS 512 #define MAX_BRUSHES 64 #define MAX_FONTS 64 static int num_brushes, num_stamps; static SDL_Surface * img_brushes[MAX_BRUSHES]; static SDL_Surface * img_stamps[MAX_STAMPS]; static SDL_Surface * img_stamps_premirror[MAX_STAMPS]; static char * txt_stamps[MAX_STAMPS]; static info_type * inf_stamps[MAX_STAMPS]; static state_type * state_stamps[MAX_STAMPS]; #ifndef NOSOUND static Mix_Chunk * snd_stamps[MAX_STAMPS]; #endif static SDL_Surface * img_stamp_thumbs[MAX_STAMPS], * img_stamp_thumbs_premirror[MAX_STAMPS]; 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]; #ifndef LOW_QUALITY_COLOR_SELECTOR static SDL_Surface * img_color_btns[NUM_COLORS]; #endif static SDL_Surface * img_cur_brush; static int brush_counter, rainbow_color; static TTF_Font * font, * small_font, * large_font, * locale_font; static TTF_Font * fonts[MAX_FONTS]; static int num_fonts; #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 int cur_tool, cur_color, cur_brush, cur_stamp, cur_shape, cur_magic; static int cur_font, cur_eraser; static int cursor_left, cursor_x, cursor_y, cursor_textwidth; static int colors_are_selectable; static int been_saved; static char file_id[32]; static char starter_id[32]; static int brush_scroll, stamp_scroll, font_scroll; 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; static char * langstr; static char * savedir; #ifdef USE_HQ4X static int RGBtoYUV[65536]; #endif 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 update_canvas(int x1, int y1, int x2, int y2); static void show_usage(FILE * f, char * prg); static void show_lang_usage(FILE * f, char * prg); static void show_locale_usage(FILE * f, char * prg); static void setup(int argc, char * argv[]); static SDL_Cursor * get_cursor(char * bits, char * mask_bits, int w, int h, int x, 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 SDL_Surface * loadaltimage(const char * const fname); static void draw_toolbar(void); static void draw_magic(void); static void draw_colors(int enabled); 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); #ifndef NOSOUND static void loadarbitrary(SDL_Surface * surfs[], SDL_Surface * altsurfs[], char * descs[], info_type * infs[], Mix_Chunk * sounds[], int * count, int starting, int max, const char * const dir, int fatal, int maxw, int maxh); #else static void loadarbitrary(SDL_Surface * surfs[], SDL_Surface * altsurfs[], char * descs[], info_type * infs[], int * count, int starting, int max, const char * const dir, int fatal, int maxw, int maxh); #endif static SDL_Surface * thumbnail(SDL_Surface * src, int max_x, int max_y, int keep_aspect); static Uint32 getpixel(SDL_Surface * surface, int x, int y); static void putpixel(SDL_Surface * surface, int x, int y, Uint32 pixel); static void clipped_putpixel(SDL_Surface * dest, int x, int y, Uint32 c); static void debug(const char * const str); 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); static void update_stamp_xor(void); static void stamp_xor(int x1, int y1); 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 void update_screen(int x1, int y1, int x2, int y2); static Uint8 alpha(Uint8 c1, Uint8 c2, Uint8 a); static int compare_strings(char * * s1, char * * s2); 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 info_type * loadinfo(const char * const fname); #ifndef NOSOUND static Mix_Chunk * loadsound(const char * const fname); #endif static void do_wait(void); static void load_current(void); static void save_current(void); static char * get_fname(const char * const name); static int do_prompt(const char * const text, const char * const btn_yes, const char * const btn_no); 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); static int do_open(int want_new_tool); #ifdef SCAN_FILL static void scan_fill(int cnt, point_type * pts); #endif #ifdef SCANLINE_POLY_FILL static int clip_polygon(int n, fpoint_type * pin, fpoint_type * pout); #endif static void wait_for_sfx(void); static int current_language(void); static int stamp_colorable(int stamp); static int stamp_tintable(int stamp); static int stamp_tintgray(int stamp); 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 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, int fatal); static char * uppercase(char * str); static unsigned char * textdir(const unsigned 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, int x); static int need_own_font(int l); static int need_right_to_left(int l); 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 TTF_Font *try_alternate_font(int language); static void mirror_starter(void); static void flip_starter(void); #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 /* --- MAIN --- */ int main(int argc, char * argv[]) { SDL_Surface * tmp_surf; SDL_Color black = {0, 0, 0, 0}; SDL_Rect dest; char tmp_str[128]; /* Set up locale support */ setlocale(LC_ALL, ""); /* Set up! */ setup(argc, argv); do_setcursor(cursor_arrow); SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 255, 255, 255)); dest.x = (WINDOW_WIDTH - img_title->w) / 2; dest.y = (WINDOW_HEIGHT - img_title->h); SDL_BlitSurface(img_title, NULL, screen, &dest); snprintf(tmp_str, sizeof(tmp_str), "%s – %s", VER_VERSION, VER_DATE); tmp_surf = TTF_RenderUTF8_Blended(font, tmp_str, black); dest.x = 20 + (WINDOW_WIDTH - img_title->w) / 2; dest.y = WINDOW_HEIGHT - 60; SDL_BlitSurface(tmp_surf, NULL, screen, &dest); SDL_FreeSurface(tmp_surf); SDL_Flip(screen); playsound(0, SND_HARP, 1); do_wait(); SDL_FreeSurface(img_title); /* 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; 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(1); draw_brushes(); update_canvas(0, 0, WINDOW_WIDTH - 96, (48 * 7) + 40 + HEIGHTOFFSET); SDL_Flip(screen); /* 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") enum { SHAPE_TOOL_MODE_STRETCH, SHAPE_TOOL_MODE_ROTATE, SHAPE_TOOL_MODE_DONE }; /* --- MAIN LOOP! --- */ static void mainloop(void) { int done, off_y, which, button_down, old_x, old_y, new_x, new_y, line_start_x, line_start_y, w, h, shape_tool_mode, shape_ctr_x, shape_ctr_y, shape_outer_x, shape_outer_y; int num_things, thing_scroll, cur_thing, old_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 = 0; thing_scroll = 0; cur_thing = 0; old_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) { 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 || mod & KMOD_LCTRL || mod & KMOD_RCTRL) && !noshortcuts) { /* Ctrl-Z - Undo */ if (tool_avail[TOOL_UNDO]) { if (cur_undo == newest_undo) { rec_undo_buffer(); do_undo(); } do_undo(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); shape_tool_mode = SHAPE_TOOL_MODE_DONE; } } else if (key == SDLK_r && (mod & KMOD_CTRL || mod & KMOD_LCTRL || mod & KMOD_RCTRL) && !noshortcuts) { /* Ctrl-R - Redo */ if (tool_avail[TOOL_REDO]) { do_redo(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); shape_tool_mode = SHAPE_TOOL_MODE_DONE; } } else if (key == SDLK_o && (mod & KMOD_CTRL || mod & KMOD_LCTRL || mod & KMOD_RCTRL) && !noshortcuts) { /* Ctrl-O - Open */ tmp_int = tool_avail[TOOL_NEW]; disable_avail_tools(); draw_toolbar(); draw_colors(0); draw_none(); tmp_int = do_open(tmp_int); enable_avail_tools(); tool_avail[TOOL_NEW] = tmp_int; draw_toolbar(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES || cur_tool == TOOL_SHAPES || cur_tool == TOOL_TEXT) { draw_colors(1); } else if (cur_tool == TOOL_STAMP) { draw_colors(stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)); } else if (cur_tool == TOOL_MAGIC && cur_magic == MAGIC_FILL) { draw_colors(1); } 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 || mod & KMOD_LCTRL || mod & KMOD_RCTRL))) && tool_avail[TOOL_NEW] && !noshortcuts) { /* Ctrl-N - New */ if (do_prompt(PROMPT_NEW_TXT, PROMPT_NEW_YES, PROMPT_NEW_NO)) { 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } else if (key == SDLK_s && (mod & KMOD_CTRL || mod & KMOD_LCTRL || mod & KMOD_RCTRL) && !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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } 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, 1); do_render_cur_text(0); } } else if (key_down == SDLK_RETURN) { if (texttool_len > 0) { rec_undo_buffer(); do_render_cur_text(1); texttool_len = 0; cursor_textwidth = 0; } cursor_x = cursor_left; cursor_y = cursor_y + TTF_FontHeight(fonts[cur_font]); if (cursor_y > ((48 * 7 + 40 + HEIGHTOFFSET) - TTF_FontHeight(fonts[cur_font]))) { cursor_y = ((48 * 7 + 40 + HEIGHTOFFSET) - TTF_FontHeight(fonts[cur_font])); } playsound(0, SND_RETURN, 1); } else if (isprint(key_unicode)) { if (texttool_len < sizeof(texttool_str) - MAX_UTF8_CHAR_LENGTH) { #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'; playsound(0, SND_KEYCLICK, 1); do_render_cur_text(0); } } } } } else if ((event.type == SDL_MOUSEBUTTONDOWN && event.button.button >= 1 && event.button.button <= 3)) { if (/* event.button.x >= 0 && */ event.button.x < 96 && event.button.y >= 40 && event.button.y < (48 * 7) + 40) /* FIXME: FIX ME? */ { /* A tool on the left has been pressed! */ which = ((event.button.y - 40) / 48) * 2 + (event.button.x / 48); 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); playsound(1, SND_CLICK, 0); 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; draw_brushes(); draw_colors(1); } else if (cur_tool == TOOL_ERASER) { cur_thing = cur_eraser; draw_erasers(); draw_colors(0); } else if (cur_tool == TOOL_STAMP) { cur_thing = cur_stamp; draw_stamps(); draw_colors(stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)); update_stamp_xor(); } else if (cur_tool == TOOL_LINES) { cur_thing = cur_brush; draw_brushes(); draw_colors(1); } else if (cur_tool == TOOL_SHAPES) { cur_thing = cur_shape; draw_shapes(); draw_colors(1); shape_tool_mode = SHAPE_TOOL_MODE_DONE; } else if (cur_tool == TOOL_TEXT) { cur_thing = cur_font; draw_fonts(); draw_colors(1); } else if (cur_tool == TOOL_ERASER) { cur_thing = cur_eraser; draw_erasers(); draw_colors(0); } 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); shape_tool_mode = SHAPE_TOOL_MODE_DONE; } else if (cur_tool == TOOL_OPEN) { tmp_int = tool_avail[TOOL_NEW]; disable_avail_tools(); draw_toolbar(); draw_colors(0); draw_none(); tmp_int = do_open(tmp_int); enable_avail_tools(); tool_avail[TOOL_NEW] = tmp_int; cur_tool = old_tool; draw_toolbar(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); draw_tux_text(TUX_GREAT, tool_tips[cur_tool], 1); if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES || cur_tool == TOOL_SHAPES || cur_tool == TOOL_TEXT) { draw_colors(1); } else if (cur_tool == TOOL_STAMP) { draw_colors(stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)); } else if (cur_tool == TOOL_MAGIC && cur_magic == MAGIC_FILL) { draw_colors(1); } 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } else if (cur_tool == TOOL_NEW) { if (do_prompt(PROMPT_NEW_TXT, PROMPT_NEW_YES, PROMPT_NEW_NO)) { 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } 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 (do_prompt(PROMPT_PRINT_NOW_TXT, PROMPT_PRINT_NOW_YES, PROMPT_PRINT_NOW_NO)) { do_print(); last_print_time = cur_time; } } else { do_prompt(PROMPT_PRINT_TOO_SOON_TXT, PROMPT_PRINT_TOO_SOON_YES, ""); } cur_tool = old_tool; draw_toolbar(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } else if (cur_tool == TOOL_MAGIC) { cur_thing = cur_magic; rainbow_color = 0; draw_magic(); if (cur_magic == MAGIC_FILL) draw_colors(1); else draw_colors(0); } else if (cur_tool == TOOL_QUIT) { done = do_quit(); cur_tool = old_tool; draw_toolbar(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); SDL_UpdateRect(screen, 0, (48 * 7) + 40 + HEIGHTOFFSET, WINDOW_WIDTH, 48); } } else if (event.button.x >= WINDOW_WIDTH - 96 && event.button.x < WINDOW_WIDTH && event.button.y >= 40 && event.button.y < (48 * (7 + TOOLOFFSET / 2)) + 40) { /* Options on the right have been pressed! */ 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) { /* Note set of things we're dealing with */ /* (stamps, brushes, etc.) */ if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES) { num_things = num_brushes; thing_scroll = brush_scroll; } else if (cur_tool == TOOL_STAMP) { num_things = num_stamps; thing_scroll = stamp_scroll; } else if (cur_tool == TOOL_TEXT) { num_things = num_fonts; thing_scroll = font_scroll; } else if (cur_tool == TOOL_SHAPES) { num_things = NUM_SHAPES; thing_scroll = 0; } else if (cur_tool == TOOL_MAGIC) { num_things = NUM_MAGICS; thing_scroll = 0; } else if (cur_tool == TOOL_ERASER) { num_things = NUM_ERASERS; thing_scroll = 0; } do_draw = 0; /* Deal with scroll buttons: */ max = 14; if (cur_tool == TOOL_STAMP && !disable_stamp_controls) max = 10; if (num_things > max + TOOLOFFSET) { if (event.button.y < 40 + 24) { if (thing_scroll > 0) { thing_scroll = thing_scroll - 2; do_draw = 1; playsound(1, SND_SCROLL, 1); if (scrolling == 0) { 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 == 1) { SDL_RemoveTimer(scrolltimer); scrolling = 0; } } } } 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) { if (thing_scroll < num_things - (max - 2) - TOOLOFFSET) { thing_scroll = thing_scroll + 2; do_draw = 1; playsound(1, SND_SCROLL, 1); if (scrolling == 0) { 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 == 1) { SDL_RemoveTimer(scrolltimer); scrolling = 0; } } } } off_y = 24; } else { off_y = 0; } if (event.button.y > 40 + off_y && event.button.y < (48 * ((max / 2) + TOOLOFFSET / 2)) + 40 - off_y) { which = ((event.button.y - 40 - off_y) / 48) * 2 + ((event.button.x - (WINDOW_WIDTH - 96)) / 48) + thing_scroll; if (which < num_things) { #ifndef NOSOUND if (cur_tool != TOOL_STAMP || snd_stamps[which] == NULL) { playsound(1, SND_BLEEP, 0); } #endif old_thing = cur_thing; cur_thing = which; do_draw = 1; } else { cur_thing = num_things - 1; do_draw = 1; } } else if (cur_tool == TOOL_STAMP && event.button.y >= (48 * ((max / 2) + TOOLOFFSET / 2)) + 40 && event.button.y < (48 * ((max / 2) + TOOLOFFSET / 2)) + 40 + 96 && !disable_stamp_controls) { /* Stamp controls! */ if (event.button.y >= (48 * ((max / 2) + TOOLOFFSET / 2)) + 40 && event.button.y < (48 * ((max / 2) + TOOLOFFSET / 2)) + 40 + 48) { /* One of the top buttons: */ if (event.button.x >= WINDOW_WIDTH - 96 && event.button.x <= WINDOW_WIDTH - 48) { /* Top left button: Mirror: */ if (inf_stamps[cur_stamp]->mirrorable) { state_stamps[cur_stamp]->mirrored = !state_stamps[cur_stamp]->mirrored; playsound(0, SND_MIRROR, 0); draw_stamps(); SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); } } else { /* Top right button: Flip: */ if (inf_stamps[cur_stamp]->flipable) { state_stamps[cur_stamp]->flipped = !state_stamps[cur_stamp]->flipped; playsound(0, SND_FLIP, 0); draw_stamps(); SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); } } } else { /* One of the bottom buttons: */ if (event.button.x >= WINDOW_WIDTH - 96 && event.button.x <= WINDOW_WIDTH - 48) { /* Bottom left button: Shrink: */ if (state_stamps[cur_stamp]->size > MIN_STAMP_SIZE) { state_stamps[cur_stamp]->size--; playsound(0, SND_SHRINK, 0); draw_stamps(); SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); } } else { /* Bottom right button: Grow: */ if (state_stamps[cur_stamp]->size < MAX_STAMP_SIZE) { state_stamps[cur_stamp]->size++; playsound(0, SND_GROW, 0); draw_stamps(); SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); } } } } /* Assign the change(s), if any / redraw, if needed: */ if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES) { cur_brush = cur_thing; brush_scroll = thing_scroll; 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) { cur_font = cur_thing; font_scroll = thing_scroll; if (do_draw) draw_fonts(); do_render_cur_text(0); } else if (cur_tool == TOOL_STAMP) { #ifndef NOSOUND if (cur_stamp != cur_thing || stamp_scroll == thing_scroll) { /* Only play when picking a different stamp, not simply scrolling */ if (snd_stamps[cur_thing] != NULL) Mix_PlayChannel(2, snd_stamps[cur_thing], 0); } #endif cur_stamp = cur_thing; stamp_scroll = thing_scroll; update_stamp_xor(); if (do_draw) draw_stamps(); if (txt_stamps[cur_stamp] != NULL) { #ifdef DEBUG printf("txt_stamps[cur_stamp] = %s\n", txt_stamps[cur_stamp]); #endif draw_tux_text(TUX_GREAT, txt_stamps[cur_stamp], 1); } else draw_tux_text(TUX_GREAT, "", 0); /* Enable or disable color selector: */ if ((stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)) != (stamp_colorable(old_thing) || stamp_tintable(old_thing))) { draw_colors(stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)); SDL_UpdateRect(screen, 0, (48 * 7) + 40 + HEIGHTOFFSET, WINDOW_WIDTH, 48); } } 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) { if (cur_thing == MAGIC_FILL) draw_colors(1); else draw_colors(0); SDL_UpdateRect(screen, 0, (48 * 7) + 40 + HEIGHTOFFSET, WINDOW_WIDTH, 48); } cur_magic = cur_thing; draw_tux_text(TUX_GREAT, magic_tips[cur_magic], 1); if (do_draw) draw_magic(); } /* Update the screen: */ if (do_draw) SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); } } else if (event.button.x > 96 && /* FIXME: Need exact number here! */ event.button.x < WINDOW_WIDTH && event.button.y > (48 * (7 + TOOLOFFSET / 2)) + 40 && event.button.y <= (48 * (7 + TOOLOFFSET / 2)) + 48 + 48 && (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES || cur_tool == TOOL_SHAPES || cur_tool == TOOL_TEXT || (cur_tool == TOOL_MAGIC && cur_magic == MAGIC_FILL) || (cur_tool == TOOL_STAMP && (stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp))))) { /* Color! */ which = ((event.button.x - 96) / ((WINDOW_WIDTH - 96) / NUM_COLORS)); if (which < NUM_COLORS) { cur_color = which; playsound(1, SND_BUBBLE, 1); draw_colors(1); SDL_UpdateRect(screen, 0, (48 * 7) + 40 + HEIGHTOFFSET, WINDOW_WIDTH, 48); render_brush(); draw_tux_text(TUX_KISS, color_names[cur_color], 1); if (cur_tool == TOOL_TEXT) do_render_cur_text(0); } } else if (event.button.x > 96 && event.button.x < WINDOW_WIDTH - 96 && /* event.button.y >= 0 && */ event.button.y < (48 * 7) + 40 + HEIGHTOFFSET) { /* Draw something! */ old_x = event.button.x - 96; old_y = event.button.y; if (been_saved) { been_saved = 0; if (!disable_save) tool_avail[TOOL_SAVE] = 1; draw_toolbar(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } 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); #ifdef LOW_QUALITY_STAMP_OUTLINE // the +1 is for rounding, probably needed only one side though rect_xor(old_x - (CUR_STAMP_W+1)/2, old_y - (CUR_STAMP_H+1)/2, old_x + (CUR_STAMP_W+1)/2, old_y + (CUR_STAMP_H+1)/2); #else stamp_xor(old_x, old_y); #endif playsound(1, SND_STAMP, 1); draw_tux_text(TUX_GREAT, great_str(), 1); /* FIXME: Make delay configurable: */ control_drawtext_timer(1000, txt_stamps[cur_stamp]); } 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 - 96, event.button.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]), getpixel(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; /* Make sure these commands are available now: */ if (tool_avail[TOOL_NEW] == 0) { tool_avail[TOOL_NEW] = 1; draw_toolbar(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } } } else if (event.type == SDL_MOUSEBUTTONDOWN && wheely && event.button.button >= 4 && event.button.button <= 5) { 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) { /* Note set of things we're dealing with */ /* (stamps, brushes, etc.) */ if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES) { num_things = num_brushes; thing_scroll = brush_scroll; } else if (cur_tool == TOOL_STAMP) { num_things = num_stamps; thing_scroll = stamp_scroll; } else if (cur_tool == TOOL_TEXT) { num_things = num_fonts; thing_scroll = font_scroll; } else if (cur_tool == TOOL_SHAPES) { num_things = NUM_SHAPES; thing_scroll = 0; } else if (cur_tool == TOOL_MAGIC) { num_things = NUM_MAGICS; thing_scroll = 0; } do_draw = 0; /* Deal with scroll wheels: */ max = 14; if (cur_tool == TOOL_STAMP && !disable_stamp_controls) max = 10; if (num_things > max + TOOLOFFSET) { if (event.button.button == 4) { /* Wheelmouse - UP "button" */ if (thing_scroll > 0) { thing_scroll = thing_scroll - 2; do_draw = 1; playsound(1, SND_SCROLL, 1); if (thing_scroll == 0) do_setcursor(cursor_arrow); } } else if (event.button.button == 5) { /* Wheelmouse - DOWN "button" */ if (thing_scroll < num_things - (max - 2)) { thing_scroll = thing_scroll + 2; do_draw = 1; playsound(1, SND_SCROLL, 1); if (thing_scroll == 0) do_setcursor(cursor_arrow); } } off_y = 24; } else { off_y = 0; } } /* Assign the change(s), if any / redraw, if needed: */ if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES) { cur_brush = cur_thing; brush_scroll = thing_scroll; 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) { cur_font = cur_thing; font_scroll = thing_scroll; if (do_draw) draw_fonts(); do_render_cur_text(0); } else if (cur_tool == TOOL_STAMP) { #ifndef NOSOUND if (cur_stamp != cur_thing) { /* Only play when picking a different stamp, not simply scrolling */ if (snd_stamps[cur_thing] != NULL) Mix_PlayChannel(2, snd_stamps[cur_thing], 0); } #endif cur_stamp = cur_thing; stamp_scroll = thing_scroll; update_stamp_xor(); if (do_draw) draw_stamps(); if (txt_stamps[cur_stamp] != NULL) { draw_tux_text(TUX_GREAT, txt_stamps[cur_stamp], 1); } else draw_tux_text(TUX_GREAT, "", 0); /* Enable or disable color selector: */ if ((stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)) != (stamp_colorable(old_thing) || stamp_tintable(old_thing))) { draw_colors(stamp_colorable(cur_stamp) || stamp_tintable(cur_stamp)); SDL_UpdateRect(screen, 0, (48 * 7) + 40 + HEIGHTOFFSET, WINDOW_WIDTH, 48); } } 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) { if (cur_thing == MAGIC_FILL) draw_colors(1); else draw_colors(0); SDL_UpdateRect(screen, 0, (48 * 7) + 40 + HEIGHTOFFSET, WINDOW_WIDTH, 48); } cur_magic = cur_thing; draw_tux_text(TUX_GREAT, magic_tips[cur_magic], 1); if (do_draw) draw_magic(); } /* Update the screen: */ if (do_draw) SDL_UpdateRect(screen, WINDOW_WIDTH - 96, 0, 96, (48 * 7) + 40 + HEIGHTOFFSET); } 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 - 96, event.button.y, 1); brush_draw(event.button.x - 96, event.button.y, event.button.x - 96, event.button.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 - 96; shape_outer_y = event.button.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 - 96; new_y = event.button.y; /* FIXME: Is doing this every event too intensive? */ /* Should I check current cursor first? */ if (event.button.x < 96 && event.button.y < (48 * 7) + 40 && event.button.y > 40) { /* Tools: */ if (tool_avail[(event.button.x / 48) + ((event.button.y - 40) / 48) * 2]) { do_setcursor(cursor_hand); } else { do_setcursor(cursor_arrow); } } else if (event.button.x > 96 && event.button.y >= (48 * 7) + 40 + HEIGHTOFFSET && event.button.y <= (48 * 7) + 40 + 48 + HEIGHTOFFSET) { /* Color picker: */ if (colors_are_selectable) do_setcursor(cursor_hand); else do_setcursor(cursor_arrow); } else if (event.button.x >= WINDOW_WIDTH - 96 && event.button.y > 40 && event.button.y <= (48 * (7 + TOOLOFFSET / 2)) + 40) { /* Selector: */ /* Note set of things we're dealing with */ /* (stamps, brushes, etc.) */ if (cur_tool == TOOL_BRUSH || cur_tool == TOOL_LINES) { num_things = num_brushes; thing_scroll = brush_scroll; } else if (cur_tool == TOOL_STAMP) { num_things = num_stamps; thing_scroll = stamp_scroll; } else if (cur_tool == TOOL_TEXT) { num_things = num_fonts; thing_scroll = font_scroll; } else if (cur_tool == TOOL_SHAPES) { num_things = NUM_SHAPES; thing_scroll = 0; } else if (cur_tool == TOOL_MAGIC) { num_things = NUM_MAGICS; thing_scroll = 0; } max = 14; if (cur_tool == TOOL_STAMP && !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 (event.button.x > 96 && event.button.x < WINDOW_WIDTH - 96 && event.button.y < (48 * 7) + 40 + HEIGHTOFFSET) { /* 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 + 96, line_start_y, old_x + 96, old_y); update_screen(line_start_x + 96, line_start_y, new_x + 96, new_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)) { /* Moving: Draw XOR where stamp/eraser will apply: */ if (cur_tool == TOOL_STAMP) { w = img_stamps[cur_stamp]->w; h = img_stamps[cur_stamp]->h; } else if (cur_tool == TOOL_ERASER) { w = (ERASER_MIN + ((NUM_ERASERS - cur_eraser - 1) * ((ERASER_MAX - ERASER_MIN) / (NUM_ERASERS - 1)))); h = w; } if (old_x >= 0 && old_x < WINDOW_WIDTH - 96 - 96 && old_y >= 0 && old_y < (48 * 7) + 40 + HEIGHTOFFSET) { if (cur_tool == TOOL_STAMP) { #ifndef LOW_QUALITY_STAMP_OUTLINE stamp_xor(old_x, old_y); #else rect_xor(old_x - (CUR_STAMP_W+1)/2, old_y - (CUR_STAMP_H+1)/2, old_x + (CUR_STAMP_W+1)/2, old_y + (CUR_STAMP_H+1)/2); #endif update_screen(old_x - (CUR_STAMP_W+1)/2 + 96, old_y - (CUR_STAMP_H+1)/2, old_x + (CUR_STAMP_W+1)/2 + 96, old_y + (CUR_STAMP_H+1)/2); } 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 + 96, old_y - h / 2, old_x + w / 2 + 96, old_y + h / 2); } } if (new_x >= 0 && new_x < WINDOW_WIDTH - 96 - 96 && new_y >= 0 && new_y < (48 * 7) + 40 + HEIGHTOFFSET) { if (cur_tool == TOOL_STAMP) { #ifndef LOW_QUALITY_STAMP_OUTLINE stamp_xor(new_x, new_y); #else rect_xor(old_x - (CUR_STAMP_W+1)/2, old_y - (CUR_STAMP_H+1)/2, old_x + (CUR_STAMP_W+1)/2, old_y + (CUR_STAMP_H+1)/2); #endif update_screen(old_x - (CUR_STAMP_W+1)/2 + 96, old_y - (CUR_STAMP_H+1)/2, old_x + (CUR_STAMP_W+1)/2 + 96, old_y + (CUR_STAMP_H+1)/2); } 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 + 96, new_y - h / 2, new_x + w / 2 + 96, new_y + h / 2); } } } 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(fonts[cur_font])); update_screen(cursor_x + 96 + cursor_textwidth, cursor_y, cursor_x + 96 + cursor_textwidth, cursor_y + TTF_FontHeight(fonts[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; 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); double u,v; 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; // 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 multichan dst; 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); double satratio = dst.sat / key_color.sat; double slope = (dst.L-key_color.L)/dst.sat; 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( (oldhueupper_hue_1) && (oldhueupper_hue_2) ) { putpixel(out, xx, yy, SDL_MapRGBA(out->format, mc->or, mc->og, mc->ob, mc->alpha)); continue; } // Modify the pixel double old_sat = mc->sat; double newsat = old_sat * satratio; double 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 double X,Y,Z; double u_prime, v_prime; /* temp, part of official formula */ unsigned tries = 3; trysat:; double u = newsat * sin(dst.hue); double 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 double r = 3.2410*X + -1.5374*Y + -0.4986*Z; double g = -0.9692*X + 1.8760*Y + 0.0416*Z; double 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 i, double *hue_range_ptr) { // find the most saturated pixel near the initial hue guess multichan *key_color_ptr = NULL; double hue_range; switch (inf_stamps[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:; double max_sat = 0; double lower_hue_1 = initial_hue - hue_range; double upper_hue_1 = initial_hue + hue_range; double lower_hue_2; double upper_hue_2; 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; } while (i--) { multichan *mc = work+i; // if not in the first range, and not in the second range, skip this one if( (mc->huehue>upper_hue_1) && (mc->huehue>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; 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; SDL_GetRGBA(getpixel(in, xx, yy), in->format, &r8, &g8, &b8, &a8); // get the linear greyscale value double 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 = malloc(sizeof *work * width * height); double initial_hue = tint_part_1(work, surf_ptr); double hue_range; multichan *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); } else { fprintf(stderr, "fallback to tinter=vector, this should be in the *.dat file\n"); vector_tint_surface(tmp_surf, surf_ptr); } free(work); } ////////////////////////////////////////////////////////////////////// /* Draw using the current stamp: */ static void stamp_draw(int x, int y) { SDL_Rect src, 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; /* Use a pre-mirrored version, if there is one? */ if (state_stamps[cur_stamp]->mirrored) { if (img_stamps_premirror[cur_stamp] != NULL) { /* Use pre-mirrored one! */ surf_ptr = img_stamps_premirror[cur_stamp]; } else { /* Use normal (only) one, and mirror it ourselves: */ surf_ptr = img_stamps[cur_stamp]; } } else { /* Not mirrored: */ surf_ptr = img_stamps[cur_stamp]; } /* 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; } /* Alter the stamp's color, if needed: */ if (stamp_colorable(cur_stamp)) { /* 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? (Why? Because I'm LAZY! :^) ) */ /* 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 (inf_stamps[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! */ #ifdef USE_HQ4X if (CAN_USE_HQ4X) { /* Use high quality 4x filter! */ /* Make the new surface for the scaled image: */ amask = ~(img_stamps[cur_stamp]->format->Rmask | img_stamps[cur_stamp]->format->Gmask | img_stamps[cur_stamp]->format->Bmask); final_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, img_stamps[cur_stamp]->w * 4, img_stamps[cur_stamp]->h * 4, img_stamps[cur_stamp]->format->BitsPerPixel, img_stamps[cur_stamp]->format->Rmask, img_stamps[cur_stamp]->format->Gmask, img_stamps[cur_stamp]->format->Bmask, amask); if (final_surf == 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); } hq4x_32(tmp_surf, final_surf, RGBtoYUV); } else #endif { 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! */ if (state_stamps[cur_stamp]->flipped) { /* Flipped! */ if (state_stamps[cur_stamp]->mirrored && img_stamps_premirror[cur_stamp] == NULL) { /* Mirrored, too! */ for (yy = 0; yy < final_surf->h; yy++) { for (xx = 0; xx < final_surf->w; xx++) { src.x = final_surf->w - 1 - xx; src.y = final_surf->h - 1 - yy; src.w = 1; src.h = 1; dest.x = base_x + xx; dest.y = base_y + yy; SDL_BlitSurface(final_surf, &src, canvas, &dest); } } } else { /* (Only flipped) */ for (yy = 0; yy < final_surf->h; yy++) { src.x = 0; src.y = final_surf->h - 1 - yy; src.w = final_surf->w; src.h = 1; dest.x = base_x; dest.y = base_y + yy; SDL_BlitSurface(final_surf, &src, canvas, &dest); } } } else { if (state_stamps[cur_stamp]->mirrored && img_stamps_premirror[cur_stamp] == NULL) { /* Mirrored! */ for (xx = 0; xx < final_surf->w; xx++) { src.x = final_surf->w - 1 - xx; src.y = 0; src.w = 1; src.h = final_surf->h; dest.x = base_x + xx; dest.y = base_y; SDL_BlitSurface(final_surf, &src, canvas, &dest); } } else { /* Not altered at all */ 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_THICK) playsound(0, SND_THICK, 0); else if (cur_magic == MAGIC_THIN) playsound(0, SND_THIN, 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_RAINBOW) playsound(0, SND_RAINBOW, 0); /* FIXME: Arbitrary? */ update_canvas(orig_x1 - 32, orig_y1 - 32, orig_x2 + 32, orig_y2 + 32); } /* 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, r1, g1, b1, r2, g2, b2, r3, g3, b3, r4, g4, b4; SDL_Surface * last; SDL_Rect src, dest; int undo_ctr; /* 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]; brush_counter++; if (brush_counter >= 4) /* FIXME: Arbitrary? */ { brush_counter = 0; if (cur_magic == MAGIC_BLUR) { /* FIXME: Circular "brush?" */ SDL_LockSurface(canvas); for (yy = y - 16; yy < y + 16; yy = yy + 2) { for (xx = x - 16; xx < x + 16; xx = xx + 2) { SDL_GetRGB(getpixel(canvas, clamp(0, xx, canvas->w - 1), clamp(0, yy - 1, canvas->h - 1)), canvas->format, &r1, &g1, &b1); SDL_GetRGB(getpixel(canvas, clamp(0, xx - 1, canvas->w - 1), clamp(0, yy, canvas->h - 1)), canvas->format, &r2, &g2, &b2); SDL_GetRGB(getpixel(canvas, clamp(0, xx + 1, canvas->w - 1), clamp(0, yy, canvas->h - 1)), canvas->format, &r3, &g3, &b3); SDL_GetRGB(getpixel(canvas, clamp(0, xx, canvas->w - 1), clamp(0, yy + 1, canvas->h - 1)), canvas->format, &r4, &g4, &b4); r = (r1 + r2 + r3 + r4) >> 2; g = (g1 + g2 + g3 + g4) >> 2; b = (b1 + b2 + b3 + b4) >> 2; putpixel(canvas, xx, yy, SDL_MapRGB(canvas->format, r, g, b)); } } 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, 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_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, 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, 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) { SDL_LockSurface(last); SDL_LockSurface(canvas); for (yy = y - 16; yy < y + 16; yy++) { for (xx = x - 16; xx < x + 16; xx++) { /* Get average color around here: */ SDL_GetRGB(getpixel(last, xx, yy), last->format, &r, &g, &b); r = min(r + 48, 255); g = min(g + 48, 255); b = min(b + 48, 255); putpixel(canvas, xx, yy, SDL_MapRGB(canvas->format, r, g, b)); } } 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, 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_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(); } 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); } } } /* 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } } /* 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); } dest.x = 96; dest.y = 0; SDL_BlitSurface(canvas, NULL, screen, &dest); update_screen(x1 + 96, y1, x2 + 96, y2); } /* 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 [--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 [--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, blank, #ifdef WIN32 blank, #endif blank); free(blank); } /* FIXME: All this should REALLY be array-based!!! */ /* Show available languages: */ static void show_lang_usage(FILE * f, char * 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" /* fi */ " finnish suomi\n" /* fr */ " french francais\n" /* gl */ " galician galego\n" /* de */ " german deutsch\n" /* el */ " greek\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" /* tlh */ " klingon tlhIngan\n" /* ko */ " korean\n" /* lt */ " lithuanian lietuviu\n" /* ms */ " malay\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" /* sv */ " swedish svenska\n" /* ta */ " tamil\n" /* tr */ " turkish\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, char * 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" " gl_ES (Galician Galego)\n" " de_DE (German Deutsch)\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" " 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" " sk_SK (Slovak)\n" " sl_SI (Slovenian)\n" " sq_AL (Albanian)\n" " sr_YU (Serbian)\n" " es_ES (Spanish Espanol)\n" " sv_SE (Swedish Svenska)\n" " tr_TR (Turkish)\n" " vi_VN (Vietnamese)\n" " wa_BE (Walloon)\n" " cy_GB (Welsh Cymraeg)\n" "\n", prg); } 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; } // The original Tux Paint canvas was 608x472. 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(608*608+472*472); 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; } /* Setup: */ static void setup(int argc, char * argv[]) { int i, ok_to_use_sysconfig, ok_to_use_lockfile; 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; Uint8 r, g, b, a; #endif SDL_Surface * tmp_imgcurup, * tmp_imgcurdown; #ifdef __BEOS__ /* 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). */ if (argc && argv[0]) { char * slash = strrchr(argv[0], '/'); *(slash + 1) = '\0'; chdir(argv[0]); *(slash + 1) = '/'; } #endif /* Set default options: */ use_sound = 1; fullscreen = 0; noshortcuts = 0; dont_do_xor = 0; keymouse = 0; wheely = 1; grab_input = 0; no_fancy_cursors = 0; simple_shapes = 0; only_uppercase = 0; promptless_save = SAVE_OVER_PROMPT; disable_quit = 0; disable_save = 0; disable_print = 0; dont_load_stamps = 0; print_delay = 0; printcommand = "pngtopnm | pnmtops | lpr"; langstr = NULL; use_print_config = 0; mirrorstamps = 0; disable_stamp_controls = 0; WINDOW_WIDTH = 640; WINDOW_HEIGHT = 480; 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 = strdup("userdata"); #elif __BEOS__ savedir = strdup("./userdata"); #else savedir = NULL; #endif /* Load options from global config file: */ #ifndef WIN32 /* 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) { snprintf(str, sizeof(str), "%s/tuxpaint.conf", CONFDIR); fi = fopen(str, "r"); if (fi != NULL) { parse_options(fi); fclose(fi); } else debug(str); } #endif /* Load options from user's own configuration (".rc" / ".cfg") file: */ #if defined(WIN32) || defined(__BEOS__) /* Windows and 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], "--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], "--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) { snprintf(str, sizeof(str), "LANG=%s", argv[i + 1]); putenv(str); setlocale(LC_ALL, ""); /* argv[i + 1]) ? */ 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]) { if (langstr != NULL) free(langstr); langstr = strdup(argv[i] + 7); } else if (strcmp(argv[i], "--lang") == 0 || strcmp(argv[i], "-l") == 0) { if (i < argc - 1) { if (langstr != NULL) free(langstr); langstr = strdup(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); } } /* Set up language: */ if (langstr == NULL && getenv("LANG") != NULL && strncasecmp(getenv("LANG"), "lt_LT", 5) == 0) { langstr = strdup("lithuanian"); } if (langstr == NULL && getenv("LANG") != NULL && strncasecmp(getenv("LANG"), "pl_PL", 5) == 0) { langstr = strdup("polish"); } if (langstr != NULL) { if (strcmp(langstr, "english") == 0 || strcmp(langstr, "american-english") == 0) { putenv((char *) "LANGUAGE=C"); putenv((char *) "LC_ALL=C"); } else if (strcmp(langstr, "croatian") == 0 || strcmp(langstr, "hrvatski") == 0) { putenv((char *) "LANGUAGE=hr_HR.UTF-8"); putenv((char *) "LC_ALL=hr_HR.UTF-8"); } else if (strcmp(langstr, "catalan") == 0 || strcmp(langstr, "catala") == 0) { putenv((char *) "LANGUAGE=ca_ES.UTF-8"); putenv((char *) "LC_ALL=ca_ES.UTF-8"); } else if (strcmp(langstr, "belarusian") == 0 || strcmp(langstr, "bielaruskaja") == 0) { putenv((char *) "LANGUAGE=be_BY.UTF-8"); putenv((char *) "LC_ALL=be_BY.UTF-8"); } else if (strcmp(langstr, "czech") == 0 || strcmp(langstr, "cesky") == 0) { putenv((char *) "LANGUAGE=cs_CZ.UTF-8"); putenv((char *) "LC_ALL=cs_CZ.UTF-8"); } else if (strcmp(langstr, "danish") == 0 || strcmp(langstr, "dansk") == 0) { putenv((char *) "LANGUAGE=da_DK.UTF-8"); putenv((char *) "LC_ALL=da_DK.UTF-8"); } else if (strcmp(langstr, "german") == 0 || strcmp(langstr, "deutsch") == 0) { putenv((char *) "LANGUAGE=de_DE.UTF-8"); putenv((char *) "LC_ALL=de_DE.UTF-8"); } else if (strcmp(langstr, "greek") == 0) { putenv((char *) "LANGUAGE=el_GR.UTF-8"); putenv((char *) "LC_ALL=el_GR.UTF-8"); } else if (strcmp(langstr, "british-english") == 0 || strcmp(langstr, "british") == 0) { putenv((char *) "LANGUAGE=en_GB.UTF-8"); putenv((char *) "LC_ALL=en_GB.UTF-8"); } else if (strcmp(langstr, "spanish") == 0 || strcmp(langstr, "espanol") == 0) { putenv((char *) "LANGUAGE=es_ES.UTF-8"); putenv((char *) "LC_ALL=es_ES.UTF-8"); } else if (strcmp(langstr, "finnish") == 0 || strcmp(langstr, "suomi") == 0) { putenv((char *) "LANGUAGE=fi_FI.UTF-8"); putenv((char *) "LC_ALL=fi_FI.UTF-8"); } else if (strcmp(langstr, "french") == 0 || strcmp(langstr, "francais") == 0) { putenv((char *) "LANGUAGE=fr_FR.UTF-8"); putenv((char *) "LC_ALL=fr_FR.UTF-8"); } else if (strcmp(langstr, "galician") == 0 || strcmp(langstr, "galego") == 0) { putenv((char *) "LANGUAGE=gl_ES.UTF-8"); putenv((char *) "LC_ALL=gl_ES.UTF-8"); } else if (strcmp(langstr, "hebrew") == 0) { putenv((char *) "LANGUAGE=he_IL.UTF-8"); putenv((char *) "LC_ALL=he_IL.UTF-8"); } else if (strcmp(langstr, "hindi") == 0) { putenv((char *) "LANGUAGE=hi_IN.UTF-8"); putenv((char *) "LC_ALL=hi_IN.UTF-8"); } else if (strcmp(langstr, "hungarian") == 0 || strcmp(langstr, "magyar") == 0) { putenv((char *) "LANGUAGE=hu_HU.UTF-8"); } else if (strcmp(langstr, "indonesian") == 0 || strcmp(langstr, "bahasa-indonesia") == 0) { putenv((char *) "LANGUAGE=id_ID.UTF-8"); putenv((char *) "LC_ALL=id_ID.UTF-8"); } else if (strcmp(langstr, "icelandic") == 0 || strcmp(langstr, "islenska") == 0) { putenv((char *) "LANGUAGE=is_IS.UTF-8"); putenv((char *) "LC_ALL=is_IS.UTF-8"); } else if (strcmp(langstr, "italian") == 0 || strcmp(langstr, "italiano") == 0) { putenv((char *) "LANGUAGE=it_IT.UTF-8"); putenv((char *) "LC_ALL=it_IT.UTF-8"); } else if (strcmp(langstr, "japanese") == 0) { putenv((char *) "LANGUAGE=ja_JP.UTF-8"); putenv((char *) "LC_ALL=ja_JP.UTF-8"); } else if (strcmp(langstr, "vietnamese") == 0) { putenv((char *) "LANGUAGE=vi_VN.UTF-8"); putenv((char *) "LC_ALL=vi_VN.UTF-8"); } else if (strcmp(langstr, "afrikaans") == 0) { putenv((char *) "LANGUAGE=af_ZA.UTF-8"); putenv((char *) "LC_ALL=af_ZA.UTF-8"); } else if (strcmp(langstr, "albanian") == 0) { putenv((char *) "LANGUAGE=sq_AL.UTF-8"); putenv((char *) "LC_ALL=sq_AL.UTF-8"); } else if (strcmp(langstr, "breton") == 0 || strcmp(langstr, "brezhoneg") == 0) { putenv((char *) "LANGUAGE=br_FR.UTF-8"); putenv((char *) "LC_ALL=br_FR.UTF-8"); } else if (strcmp(langstr, "bulgarian") == 0) { putenv((char *) "LANGUAGE=bg_BG.UTF-8"); putenv((char *) "LC_ALL=bg_BG.UTF-8"); } else if (strcmp(langstr, "welsh") == 0 || strcmp(langstr, "cymraeg") == 0) { putenv((char *) "LANGUAGE=cy_GB.UTF-8"); putenv((char *) "LC_ALL=cy_GB.UTF-8"); } else if (strcmp(langstr, "bokmal") == 0) { putenv((char *) "LANGUAGE=nb_NO.UTF-8"); putenv((char *) "LC_ALL=nb_NO.UTF-8"); } else if (strcmp(langstr, "basque") == 0 || strcmp(langstr, "euskara") == 0) { putenv((char *) "LANGUAGE=eu_ES.UTF-8"); putenv((char *) "LC_ALL=eu_ES.UTF-8"); } else if (strcmp(langstr, "korean") == 0) { putenv((char *) "LANGUAGE=ko_KR.UTF-8"); putenv((char *) "LC_ALL=ko_KR.UTF-8"); } else if (strcmp(langstr, "klingon") == 0 || strcmp(langstr, "tlhIngan") == 0 || strcmp(langstr, "tlhingan") == 0) { putenv((char *) "LANGUAGE=tlh.UTF-8"); putenv((char *) "LC_ALL=tlh.UTF-8"); } else if (strcmp(langstr, "tamil") == 0) { putenv((char *) "LANGUAGE=ta_IN.UTF-8"); putenv((char *) "LC_ALL=ta_IN.UTF-8"); } else if (strcmp(langstr, "lithuanian") == 0 || strcmp(langstr, "lietuviu") == 0) { putenv((char *) "LANGUAGE=lt_LT.UTF-8"); putenv((char *) "LC_ALL=lt_LT.UTF-8"); } else if (strcmp(langstr, "malay") == 0) { putenv((char *) "LANGUAGE=ms_MY.UTF-8"); putenv((char *) "LC_ALL=ms_MY.UTF-8"); } else if (strcmp(langstr, "dutch") == 0 || strcmp(langstr, "nederlands") == 0) { putenv((char *) "LANGUAGE=nl_NL.UTF-8"); putenv((char *) "LC_ALL=nl_NL.UTF-8"); } else if (strcmp(langstr, "norwegian") == 0 || strcmp(langstr, "nynorsk") == 0 || strcmp(langstr, "norsk") == 0) { putenv((char *) "LANGUAGE=nn_NO.UTF-8"); putenv((char *) "LC_ALL=nn_NO.UTF-8"); } else if (strcmp(langstr, "polish") == 0 || strcmp(langstr, "polski") == 0) { putenv((char *) "LANGUAGE=pl_PL.UTF-8"); putenv((char *) "LC_ALL=pl_PL.UTF-8"); } else if (strcmp(langstr, "brazilian-portuguese") == 0 || strcmp(langstr, "portugues-brazilian") == 0 || strcmp(langstr, "brazilian") == 0) { putenv((char *) "LANGUAGE=pt_BR.UTF-8"); putenv((char *) "LC_ALL=pt_BR.UTF-8"); } else if (strcmp(langstr, "portuguese") == 0 || strcmp(langstr, "portugues") == 0) { putenv((char *) "LANGUAGE=pt_PT.UTF-8"); putenv((char *) "LC_ALL=pt_PT.UTF-8"); } else if (strcmp(langstr, "romanian") == 0) { putenv((char *) "LANGUAGE=ro_RO.UTF-8"); putenv((char *) "LC_ALL=ro_RO.UTF-8"); } else if (strcmp(langstr, "russian") == 0 || strcmp(langstr, "russkiy") == 0) { putenv((char *) "LANGUAGE=ru_RU.UTF-8"); putenv((char *) "LC_ALL=ru_RU.UTF-8"); } else if (strcmp(langstr, "slovak") == 0) { putenv((char *) "LANGUAGE=sk_SK.UTF-8"); putenv((char *) "LC_ALL=sk_SK.UTF-8"); } else if (strcmp(langstr, "slovenian") == 0 || strcmp(langstr, "slovensko") == 0) { putenv((char *) "LANGUAGE=sl_SI.UTF-8"); putenv((char *) "LC_ALL=sl_SI.UTF-8"); } else if (strcmp(langstr, "serbian") == 0) { putenv((char *) "LANGUAGE=sr_YU.UTF-8"); putenv((char *) "LC_ALL=sr_YU.UTF-8"); } else if (strcmp(langstr, "swedish") == 0 || strcmp(langstr, "svenska") == 0) { putenv((char *) "LANGUAGE=sv_SE.UTF-8"); putenv((char *) "LC_ALL=sv_SE.UTF-8"); } else if (strcmp(langstr, "turkish") == 0) { putenv((char *) "LANGUAGE=tr_TR.UTF-8"); putenv((char *) "LC_ALL=tr_TR.UTF-8"); } else if (strcmp(langstr, "walloon") == 0 || strcmp(langstr, "walon") == 0) { putenv((char *) "LANGUAGE=wa_BE.UTF-8"); putenv((char *) "LC_ALL=wa_BE.UTF-8"); } else if (strcmp(langstr, "chinese") == 0 || strcmp(langstr, "simplified-chinese") == 0) { putenv((char *) "LANGUAGE=zh_CN.UTF-8"); putenv((char *) "LC_ALL=zh_CN.UTF-8"); } else if (strcmp(langstr, "traditional-chinese") == 0) { putenv((char *) "LANGUAGE=zh_TW.UTF-8"); putenv((char *) "LC_ALL=zh_TW.UTF-8"); } else if (strcmp(langstr, "help") == 0 || strcmp(langstr, "list") == 0) { show_lang_usage(stdout, (char *) getfilename(argv[0])); free(langstr); exit(0); } else { fprintf(stderr, "%s is an invalid language\n", langstr); show_lang_usage(stderr, (char *) getfilename(argv[0])); free(langstr); exit(1); } setlocale(LC_ALL, ""); free(langstr); } 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"); language = current_language(); #ifdef DEBUG printf("DEBUG: Language is %s (%d)\n", lang_prefixes[language], language); #endif #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; FILE * fi; time_t time_lock, time_now; /* 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"); fclose(fi); exit(0); } } fclose(fi); } /* Okay to run; create/update the lockfile */ 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)); } } /* Init SDL Video: */ if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "\nError: I could not initialize video!\n" "The Simple DirectMedia Layer error that occurred was:\n" "%s\n\n", SDL_GetError()); exit(1); } /* Set-up Key-Repeat: */ SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); /* Init SDL Timer: */ if (SDL_Init(SDL_INIT_TIMER) < 0) { fprintf(stderr, "\nError: I could not initialize timer!\n" "The Simple DirectMedia Layer error that occurred was:\n" "%s\n\n", SDL_GetError()); exit(1); } /* 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); } /* Init SDL Audio and set-up Mixer: */ #ifndef NOSOUND if (use_sound) { if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "\nWarning: I could not initialize audio!\n" "The Simple DirectMedia Layer error that occurred was:\n" "%s\n\n", SDL_GetError()); use_sound = 0; } else { #ifndef WIN32 if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 1024) < 0) #else if (Mix_OpenAudio(44100, AUDIO_S16, 2, 2048) < 0) #endif { 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; } else { /* Load sounds: */ for (i = 0; i < NUM_SOUNDS; 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 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); } #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 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_watch = get_cursor(watch_bits, watch_mask_bits, watch_width, watch_height, 14 / scale, 14 / 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 / scale, 3 / scale); do_setcursor(cursor_watch); /* 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 and display title image: */ img_title = loadimage(DATA_PREFIX "images/title.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) / 2; dest.y = (WINDOW_HEIGHT - img_title->h); SDL_BlitSurface(img_title, NULL, screen, &dest); prog_bar_ctr = 0; show_progress_bar(); SDL_Flip(screen); #ifdef USE_HQ4X /* Init high quality scaling stuff: */ InitLUTs(RGBtoYUV); #endif /* 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"); 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_grow = loadimage(DATA_PREFIX "images/ui/grow.png"); img_shrink = loadimage(DATA_PREFIX "images/ui/shrink.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"); img_paintcan = loadimage(DATA_PREFIX "images/ui/paintcan.png"); show_progress_bar(); img_sparkles = loadimage(DATA_PREFIX "images/ui/sparkles.png"); /* Load brushes: */ #ifndef NOSOUND loadarbitrary(img_brushes, NULL, NULL, NULL, NULL, &num_brushes, 0, MAX_BRUSHES, DATA_PREFIX "brushes", 1, 40, 40); #else loadarbitrary(img_brushes, NULL, NULL, NULL, &num_brushes, 0, MAX_BRUSHES, DATA_PREFIX "brushes", 1, 40, 40); #endif homedirdir = get_fname("brushes"); #ifndef NOSOUND loadarbitrary(img_brushes, NULL, NULL, NULL, NULL, &num_brushes, num_brushes, MAX_BRUSHES, homedirdir, 0, 40, 40); #else loadarbitrary(img_brushes, NULL, NULL, NULL, &num_brushes, num_brushes, MAX_BRUSHES, homedirdir, 0, 40, 40); #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: */ font = TTF_OpenFont(DATA_PREFIX "fonts/default_font.ttf", 18 - (only_uppercase * 3)); if (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); } 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); } if (need_own_font(language)) { snprintf(str, sizeof(str), "%sfonts/locale/%s.ttf", DATA_PREFIX, lang_prefixes[language]); locale_font = TTF_OpenFont(str, 18); if (locale_font == NULL) { locale_font = try_alternate_font(language); if (locale_font == 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"); bindtextdomain("tuxpaint", LOCALEDIR); textdomain("tuxpaint"); language = current_language(); } } } if (locale_font == NULL) locale_font = font; /* Load other available fonts: */ num_fonts = 0; loadfonts(DATA_PREFIX "fonts", 1); homedirdir = get_fname("fonts"); loadfonts(homedirdir, 0); free(homedirdir); /* Load stamps: */ if (dont_load_stamps == 0) { homedirdir = get_fname("stamps"); #ifndef NOSOUND loadarbitrary(img_stamps, img_stamps_premirror, txt_stamps, inf_stamps, snd_stamps, &num_stamps, 0, MAX_STAMPS, homedirdir, 0, -1, -1); #else loadarbitrary(img_stamps, img_stamps_premirror, txt_stamps, inf_stamps, &num_stamps, 0, MAX_STAMPS, homedirdir, 0, -1, -1); #endif #ifndef NOSOUND loadarbitrary(img_stamps, img_stamps_premirror, txt_stamps, inf_stamps, snd_stamps, &num_stamps, num_stamps, MAX_STAMPS, DATA_PREFIX "stamps", 0, -1, -1); #else loadarbitrary(img_stamps, img_stamps_premirror, txt_stamps, inf_stamps, &num_stamps, num_stamps, MAX_STAMPS, DATA_PREFIX "stamps", 0, -1, -1); #endif #ifdef __APPLE__ #ifndef NOSOUND loadarbitrary(img_stamps, img_stamps_premirror, txt_stamps, inf_stamps, snd_stamps, &num_stamps, num_stamps, MAX_STAMPS, "/Library/Application Support/TuxPaint/stamps", 0, -1, -1); #else loadarbitrary(img_stamps, img_stamps_premirror, txt_stamps, inf_stamps, &num_stamps, num_stamps, MAX_STAMPS, "/Library/Application Support/TuxPaint/stamps", 0, -1, -1); #endif #endif if (num_stamps == 0) { fprintf(stderr, "\nWarning: No stamps found in " DATA_PREFIX "stamps/\n" "or %s\n\n", homedirdir); } free(homedirdir); unsigned default_stamp_size = compute_default_scale_factor(1.0); /* Create stamp thumbnails: */ for (i = 0; i < num_stamps; i++) { if (img_stamps[i]->w > 40 || img_stamps[i]->h > 40) { img_stamp_thumbs[i] = thumbnail(img_stamps[i], 40, 40, 1); } else { img_stamp_thumbs[i] = NULL; } if (img_stamps_premirror[i] != NULL && !disable_stamp_controls) { /* Also thumbnail the pre-drawn mirror version, if any: */ if (img_stamps_premirror[i]->w > 40 || img_stamps_premirror[i]->h > 40) { img_stamp_thumbs_premirror[i] = thumbnail(img_stamps_premirror[i], 40, 40, 1); } else { img_stamp_thumbs_premirror[i] = NULL; } } else { img_stamps_premirror[i] = NULL; } state_stamps[i] = malloc(sizeof(state_type)); if (inf_stamps[i] == NULL) { /* Didn't load one for this stamp, assume defaults: */ inf_stamps[i] = malloc(sizeof(info_type)); inf_stamps[i]->tintable = 0; inf_stamps[i]->colorable = 0; inf_stamps[i]->mirrorable = 1; inf_stamps[i]->tintgray = 1; inf_stamps[i]->flipable = 1; inf_stamps[i]->ratio = 1.0; inf_stamps[i]->tinter = TINTER_NORMAL; } { unsigned int upper = HARD_MAX_STAMP_SIZE; unsigned int lower = 0; do { scaleparams *s = &scaletable[upper]; int pw, ph; // proposed width and height pw = (img_stamps[i]->w * s->numer + s->denom - 1) / s->denom; ph = (img_stamps[i]->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 = (img_stamps[i]->w * s->numer + s->denom - 1) / s->denom; ph = (img_stamps[i]->h * s->numer + s->denom - 1) / s->denom; if (pw*ph > 20) break; } while (++lower < HARD_MAX_STAMP_SIZE); if(upperratio != 1.0) mid = compute_default_scale_factor(inf_stamps[i]->ratio); if(mid > upper) mid = upper; if(mid < lower) mid = lower; state_stamps[i]->min = lower; state_stamps[i]->size = mid; state_stamps[i]->max = upper; } /* If Tux Paint is in mirror-image-by-default mode, mirror, if we can: */ if (mirrorstamps && inf_stamps[i]->mirrorable) state_stamps[i]->mirrored = 1; else state_stamps[i]->mirrored = 0; state_stamps[i]->flipped = 0; show_progress_bar(); } } /* 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(); } /* Create toolbox and selector labels: */ for (i = 0; i < NUM_TITLES; i++) { if (strlen(title_names[i]) > 0) { if (need_own_font(language) && locale_font != NULL && strcmp(gettext(title_names[i]), title_names[i]) != 0) { tmp_surf = TTF_RenderUTF8_Blended(locale_font, textdir(gettext(title_names[i])), black); img_title_names[i] = thumbnail(tmp_surf, min(84, tmp_surf->w), tmp_surf->h, 0); SDL_FreeSurface(tmp_surf); } else { upstr = uppercase(textdir(gettext(title_names[i]))); tmp_surf = TTF_RenderUTF8_Blended(large_font, upstr, black); img_title_names[i] = thumbnail(tmp_surf, min(84, tmp_surf->w), tmp_surf->h, 0); SDL_FreeSurface(tmp_surf); free(upstr); } } else { img_title_names[i] = NULL; } } /* Generate color selection buttons: */ #ifndef LOW_QUALITY_COLOR_SELECTOR /* Create appropriately-shaped buttons: */ tmp_btn = thumbnail(img_btn_down, (WINDOW_WIDTH - 96) / NUM_COLORS, 48, 0); /* Create surfaces to draw them into: */ for (i = 0; i < NUM_COLORS; i++) { img_color_btns[i] = SDL_CreateRGBSurface(screen->flags, /* (WINDOW_WIDTH - 96) / NUM_COLORS, 48, */ tmp_btn->w, tmp_btn->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! (%d of %d)\n" "The Simple DirectMedia Layer error that occurred was:\n" "%s\n\n", i + 1, NUM_COLORS, SDL_GetError()); cleanup(); exit(1); } SDL_LockSurface(img_color_btns[i]); } /* Generate the buttons based on the thumbnails: */ SDL_LockSurface(tmp_btn); for (y = 0; y < tmp_btn->h /* 48 */; y++) { for (x = 0; x < tmp_btn->w /* (WINDOW_WIDTH - 96) / NUM_COLORS */; x++) { SDL_GetRGB(getpixel(tmp_btn, x, y), tmp_btn->format, &r, &g, &b); a = 255 - ((r + g + b) / 3); for (i = 0; i < NUM_COLORS; i++) { putpixel(img_color_btns[i], x, y, SDL_MapRGB(img_color_btns[i]->format, alpha(color_hexes[i][0], 255, a), alpha(color_hexes[i][1], 255, a), alpha(color_hexes[i][2], 255, a))); } } } for (i = 0; i < NUM_COLORS; i++) SDL_UnlockSurface(img_color_btns[i]); SDL_UnlockSurface(tmp_btn); SDL_FreeSurface(tmp_btn); #endif create_button_labels(); /* Seed random-number generator: */ srand(SDL_GetTicks()); /* Enable Unicode support in SDL: */ SDL_EnableUNICODE(1); /* 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; } } /* Render a button label using the appropriate string/font: */ static SDL_Surface * do_render_button_label(const char * const label) { char * str; SDL_Surface * tmp_surf, * surf; SDL_Color black = {0, 0, 0, 0}; if (need_own_font(language) && locale_font != NULL && strcmp(gettext(label), label) != 0) { tmp_surf = TTF_RenderUTF8_Blended(locale_font, textdir(gettext(label)), black); surf = thumbnail(tmp_surf, min(48, tmp_surf->w), tmp_surf->h, 0); } else { str = uppercase(textdir(gettext(label))); tmp_surf = TTF_RenderUTF8_Blended(small_font, str, black); surf = thumbnail(tmp_surf, min(48, tmp_surf->w), tmp_surf->h, 0); free(str); 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]); /* 'Open' label: */ img_openlabels_open = do_render_button_label(textdir(gettext_noop("Open"))); /* 'Erase' label: */ img_openlabels_erase = do_render_button_label(textdir(gettext_noop("Erase"))); /* 'Back' label: */ img_openlabels_back = do_render_button_label(textdir(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(char * bits, char * mask_bits, int width, int height, int x, int y) { Uint8 b; Uint8 temp_bitmap[128], temp_bitmask[128]; 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: A hack to make 'Print' button act just like 'New' button: */ if (!disable_print) tool_avail[TOOL_PRINT] = tool_avail[TOOL_NEW]; draw_image_title(TITLE_TOOLS, 0); for (i = 0; i < NUM_TOOLS + TOOLOFFSET; i++) { dest.x = ((i % 2) * 48); dest.y = ((i / 2) * 48) + 40; if (i < NUM_TOOLS) { if (i == cur_tool) { SDL_BlitSurface(img_btn_down, NULL, screen, &dest); } else if (tool_avail[i]) { SDL_BlitSurface(img_btn_up, NULL, screen, &dest); } else { SDL_BlitSurface(img_btn_off, NULL, screen, &dest); } 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 i; SDL_Rect dest; /* FIXME: Should we worry about more than 14 magic effects? :^/ */ draw_image_title(TITLE_MAGIC, WINDOW_WIDTH - 96); for (i = 0; i < 14 + TOOLOFFSET; i++) { dest.x = WINDOW_WIDTH - 96 + ((i % 2) * 48); dest.y = ((i / 2) * 48) + 40; if (i < NUM_MAGICS) { if (i == 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; SDL_BlitSurface(img_magics[i], NULL, screen, &dest); dest.x = WINDOW_WIDTH - 96 + ((i % 2) * 48) + 4 + (40 - img_magic_names[i]->w) / 2; dest.y = ((i / 2) * 48) + 40 + 4 + (44 - img_magic_names[i]->h); SDL_BlitSurface(img_magic_names[i], NULL, screen, &dest); } else { SDL_BlitSurface(img_btn_off, NULL, screen, &dest); } } } /* Draw color selector: */ static void draw_colors(int enabled) { int i; SDL_Rect dest; dest.x = 0; dest.y = 40 + ((NUM_TOOLS / 2) * 48) + HEIGHTOFFSET; if (enabled) { SDL_BlitSurface(img_title_large_on, NULL, screen, &dest); dest.x = 0; dest.y = 40 + ((NUM_TOOLS / 2) * 48) + HEIGHTOFFSET; SDL_BlitSurface(img_title_large_on, NULL, screen, &dest); dest.x = (96 - img_title_names[TITLE_COLORS]->w) / 2; dest.y = (40 + ((NUM_TOOLS / 2) * 48) + HEIGHTOFFSET + (48 - 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, &dest); } for (i = 0; i < NUM_COLORS; i++) { #ifndef LOW_QUALITY_COLOR_SELECTOR dest.x = (i * ((WINDOW_WIDTH - 96) / NUM_COLORS)) + 96; dest.y = 40 + ((NUM_TOOLS / 2) * 48) + HEIGHTOFFSET; if (enabled) SDL_BlitSurface(img_color_btns[i], NULL, screen, &dest); else SDL_BlitSurface(img_color_btns[COLOR_WHITE], NULL, screen, &dest); #else dest.x = (i * ((WINDOW_WIDTH - 96) / NUM_COLORS)) + 96; dest.y = 40 + ((NUM_TOOLS / 2) * 48) + HEIGHTOFFSET; dest.w = ((WINDOW_WIDTH - 96) / NUM_COLORS); dest.h = 48 + HEIGHTOFFSET; if (enabled) 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)); #endif if (i == cur_color && enabled) { dest.x = (i * ((WINDOW_WIDTH - 96) / NUM_COLORS)) + 96; dest.y = 44 + ((NUM_TOOLS / 2) * 48) + HEIGHTOFFSET; SDL_BlitSurface(img_paintcan, NULL, screen, &dest); } } /* Keep track of this globally, so the cursor shape will act right */ colors_are_selectable = enabled; } /* Draw brushes: */ static void draw_brushes(void) { int i, off_y, max, brush; SDL_Rect dest; /* Draw the title: */ draw_image_title(TITLE_BRUSHES, WINDOW_WIDTH - 96); /* 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; SDL_Rect dest, src; SDL_Surface * tmp_surf; SDL_Color black = {0, 0, 0, 0}; /* Draw the title: */ draw_image_title(TITLE_LETTERS, WINDOW_WIDTH - 96); /* Do we need scrollbars? */ if (num_fonts > 14 + TOOLOFFSET) { off_y = 24; max = 12 + 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 (font_scroll < num_fonts - 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 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_fonts) { SDL_BlitSurface(img_btn_up, NULL, screen, &dest); } else { SDL_BlitSurface(img_btn_off, NULL, screen, &dest); } /* FIXME: We should render the font buttons once, at startup! */ if (font < num_fonts) { tmp_surf = TTF_RenderUTF8_Blended(fonts[font], "A", black); 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 stamps: */ static void draw_stamps(void) { int i, off_y, max, stamp, most; int base_x, base_y, xx, yy; SDL_Rect src, dest; SDL_Surface * img; /* Draw the title: */ draw_image_title(TITLE_STAMPS, WINDOW_WIDTH - 96); /* 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) { /* Draw the stamp itself: */ if (state_stamps[stamp]->mirrored && img_stamps_premirror[stamp] != NULL) { /* Use pre-drawn mirrored version! */ if (img_stamp_thumbs_premirror[stamp] != NULL) img = img_stamp_thumbs_premirror[stamp]; else img = img_stamps_premirror[stamp]; } else { /* Use normal version: */ if (img_stamp_thumbs[stamp] != NULL) img = img_stamp_thumbs[stamp]; else img = img_stamps[stamp]; } /* Where to put it? */ base_x = ((i % 2) * 48) + (WINDOW_WIDTH - 96) + ((48 - (img->w)) / 2); base_y = ((i / 2) * 48) + 40 + ((48 - (img->h)) / 2) + off_y; if (state_stamps[stamp]->mirrored && img_stamps_premirror[stamp] == NULL) { /* It's mirrored!: */ if (state_stamps[stamp]->flipped) { /* It's flipped, too! Mirror AND flip! */ for (xx = 0; xx < img->w; xx++) { for (yy = 0; yy < img->h; yy++) { src.x = xx; src.y = yy; src.w = 1; src.h = 1; dest.x = base_x + img->w - xx - 1; dest.y = base_y + img->h - yy - 1; SDL_BlitSurface(img, &src, screen, &dest); } } } else { /* Not flipped; just simple mirror-image: */ for (xx = 0; xx < img->w; xx++) { src.x = xx; src.y = 0; src.w = 1; src.h = img->h; dest.x = base_x + img->w - xx - 1; dest.y = base_y; SDL_BlitSurface(img, &src, screen, &dest); } } } else { /* It's not mirrored: */ if (state_stamps[stamp]->flipped) { /* Simple flip-image: */ for (yy = 0; yy < img->h; yy++) { src.x = 0; src.y = yy; src.w = img->w; src.h = 1; dest.x = base_x; dest.y = base_y + img->h - yy - 1; SDL_BlitSurface(img, &src, screen, &dest); } } else { /* No flip or mirror: just blit! */ dest.x = base_x; dest.y = base_y; SDL_BlitSurface(img, NULL, screen, &dest); } } } } /* Draw stamp controls: */ if (!disable_stamp_controls) { /* Show mirror button: */ dest.x = WINDOW_WIDTH - 96; dest.y = 40 + ((5 + TOOLOFFSET / 2) * 48); if (inf_stamps[cur_stamp]->mirrorable) { if (state_stamps[cur_stamp]->mirrored) SDL_BlitSurface(img_btn_down, NULL, screen, &dest); else SDL_BlitSurface(img_btn_up, NULL, screen, &dest); } else { SDL_BlitSurface(img_btn_off, 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(img_magics[MAGIC_MIRROR], NULL, screen, &dest); /* Show flip button: */ dest.x = WINDOW_WIDTH - 48; dest.y = 40 + ((5 + TOOLOFFSET / 2) * 48); if (inf_stamps[cur_stamp]->flipable) { if (state_stamps[cur_stamp]->flipped) SDL_BlitSurface(img_btn_down, NULL, screen, &dest); else SDL_BlitSurface(img_btn_up, NULL, screen, &dest); } else { SDL_BlitSurface(img_btn_off, 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(img_magics[MAGIC_FLIP], NULL, screen, &dest); /* Show shrink button: */ dest.x = WINDOW_WIDTH - 96; dest.y = 40 + ((6 + TOOLOFFSET / 2) * 48); if (state_stamps[cur_stamp]->size > MIN_STAMP_SIZE) SDL_BlitSurface(img_btn_up, NULL, screen, &dest); else SDL_BlitSurface(img_btn_off, 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(img_shrink, NULL, screen, &dest); /* Show grow button: */ dest.x = WINDOW_WIDTH - 48; dest.y = 40 + ((6 + TOOLOFFSET / 2) * 48); if (state_stamps[cur_stamp]->size < MAX_STAMP_SIZE) SDL_BlitSurface(img_btn_up, NULL, screen, &dest); else SDL_BlitSurface(img_btn_off, 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(img_grow, NULL, screen, &dest); } } /* Draw the shape selector: */ static void draw_shapes(void) { int i; SDL_Rect dest; draw_image_title(TITLE_SHAPES, WINDOW_WIDTH - 96); 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, WINDOW_WIDTH - 96); 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); } } /* Load an arbitrary set of images into an array (e.g., brushes or stamps) */ #ifndef NOSOUND static void loadarbitrary(SDL_Surface * surfs[], SDL_Surface * altsurfs[], char * descs[], info_type * infs[], Mix_Chunk * sounds[], int * count, int starting, int max, const char * const dir, int fatal, int maxw, int maxh) #else static void loadarbitrary(SDL_Surface * surfs[], SDL_Surface * altsurfs[], char * descs[], info_type * infs[], int * count, int starting, int max, const char * const dir, int fatal, int maxw, int maxh) #endif { DIR * d; struct dirent * f; struct stat sbuf; char fname[512]; int d_names_alloced; char * * d_names; int num_files, i; /* Make some space: */ d_names_alloced = 32; d_names = (char * *) malloc(sizeof(char *) * d_names_alloced); if (d_names == NULL) { fprintf(stderr, "\nError: I can't allocate memory for directory listing!\n" "The system error that occurred was: %s\n", strerror(errno)); cleanup(); exit(1); } *count = starting; /* Open the directory: */ d = opendir(dir); if (d == NULL) { if (fatal) { fprintf(stderr, "\nError: I can't find a directory of images\n" "%s\n" "The system error that occurred was: %s\n", dir, strerror(errno)); cleanup(); exit(1); } else { return; } } /* Read directory for images: */ num_files = 0; do { f = readdir(d); if (f != NULL) { d_names[num_files] = strdup(f->d_name); num_files++; if (num_files >= d_names_alloced) { d_names_alloced = d_names_alloced + 32; d_names = (char * *) realloc(d_names, sizeof(char *) * d_names_alloced); if (d_names == NULL) { fprintf(stderr, "\nError: I can't reallocate memory for directory listing!\n" "The system error that occurred was: %s\n", strerror(errno)); cleanup(); exit(1); } } } } while (f != NULL); closedir(d); qsort(d_names, num_files, sizeof(char *), (int(*)(const void *, const void *))compare_strings); /* Do something with each file (load if PNG, recurse if directory): */ for (i = 0; i < num_files && *count < max; i++) { /* Ignore things starting with "." (e.g., "." and ".." dirs): */ if (strstr(d_names[i], ".") != d_names[i]) { /* If it's a directory, recurse down into it: */ snprintf(fname, sizeof(fname), "%s/%s", dir, d_names[i]); debug(fname); stat(fname, &sbuf); if (S_ISDIR(sbuf.st_mode)) { debug("...is a directory"); #ifndef NOSOUND loadarbitrary(surfs, altsurfs, descs, infs, sounds, count, *count, max, fname, fatal, maxw, maxh); #else loadarbitrary(surfs, altsurfs, descs, infs, count, *count, max, fname, fatal, maxw, maxh); #endif } else if (strstr(d_names[i], ".png") != NULL && strstr(d_names[i], "_mirror.png") == NULL) { /* If it has ".png" in the filename, assume we can try to load it: */ surfs[*count] = loadimage(fname); if ((surfs[*count]->w <= maxw && surfs[*count]->h <= maxh) || (maxw == -1 || maxh == -1)) { /* Check for a companion ".txt" file! */ if (descs != NULL) descs[*count] = loaddesc(fname); if (infs != NULL) infs[*count] = loadinfo(fname); if (altsurfs != NULL) altsurfs[*count] = loadaltimage(fname); #ifndef NOSOUND if (use_sound) { if (sounds != NULL) sounds[*count] = loadsound(fname); } #endif *count = *count + 1; } else { fprintf(stderr, "\nWarning: Image too large (%d x %d - max: %d x %d)\n" "%s\n\n", surfs[*count]->w, surfs[*count]->h, maxw, maxh, fname); } show_progress_bar(); } } free(d_names[i]); } free(d_names); /* Give warning if too many files were found (e.g., some not loaded): */ if (*count == max) { fprintf(stderr, "\nWarning: Reached maximum images (%d) which can be stored in:\n" "%s\n\n", max, dir); } debug("loadarbitrary() ends..."); } /* 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; /* 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); } /* 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 getpixel(SDL_Surface * surface, int x, int y) { int bpp; Uint8 * p; Uint32 pixel; pixel = 0; /* get the X/Y values within the bounds of this surface */ if (x < 0) x = 0; if (x > surface->w - 1) x = surface->w - 1; if (y > surface->h - 1) y = surface->h - 1; if (y < 0) y = 0; /* SDL_LockSurface(surface); */ /* Determine bytes-per-pixel for the surface in question: */ bpp = surface->format->BytesPerPixel; /* 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 * bpp)); /* 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) */ if (bpp == 1) /* 8-bit display */ pixel = *p; else if (bpp == 2) /* 16-bit display */ pixel = *(Uint16 *)p; else if (bpp == 3) /* 24-bit display */ { /* 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; } else if (bpp == 4) /* 32-bit display */ pixel = *(Uint32 *)p; /* SDL_UnlockSurface(surface); */ return pixel; } /* Draw a single pixel into the surface: */ static void putpixel(SDL_Surface * surface, int x, int y, Uint32 pixel) { int bpp; Uint8 * p; /* Assuming the X/Y values are within the bounds of this surface... */ if (x >= 0 && y >= 0 && x < surface->w && y < surface->h) { /* SDL_LockSurface(surface); */ /* Determine bytes-per-pixel for the surface in question: */ bpp = surface->format->BytesPerPixel; /* Set a pointer to the exact location in memory of the pixel * in question: */ p = (Uint8 *) (((Uint8 *)surface->pixels) + /* Start: beginning of RAM */ (y * surface->pitch) + /* Go down Y lines */ (x * bpp)); /* Go in X pixels */ /* Set the (correctly-sized) piece of data in the surface's RAM * to the pixel value sent in: */ if (bpp == 1) *p = pixel; else if (bpp == 2) *(Uint16 *)p = pixel; else if (bpp == 3) { 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; } } else if (bpp == 4) { *(Uint32 *)p = pixel; } /* SDL_UnlockSurface(surface); */ } } /* Should really clip at the line level, but oh well... */ static void clipped_putpixel(SDL_Surface * dest, int x, int y, Uint32 c) { if (x >= 96 && x < (WINDOW_WIDTH - 96) && y >= 0 && y < (48 * 7 + 40 + HEIGHTOFFSET)) { putpixel(dest, x, y, c); } } /* Show debugging stuff: */ static void debug(const char * const str) { #ifndef DEBUG (void)str; #else fprintf(stderr, "DEBUG: %s\n", str); fflush(stderr); #endif } /* 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } 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(); SDL_UpdateRect(screen, 0, 0, 96, (48 * (7 + TOOLOFFSET / 2)) + 40); } /* Create the current brush in the current color: */ static void render_brush(void) { Uint32 amask; int x, y; Uint8 r, g, b, a; /* 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(img_brushes[cur_brush], x, y), img_brushes[cur_brush]->format, &r, &g, &b, &a); putpixel(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 (use_sound) { 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) { clipped_putpixel(screen, x1 + 96, y, 0xFFFFFFFF - getpixel(screen, x1 + 96, y)); } } x1 = x1 + dx; } } else { if (y1 > y2) { for (y = y1; y >= y2; y--) { num_drawn++; if (num_drawn < 10 || dont_do_xor == 0) { clipped_putpixel(screen, x1 + 96, y, 0xFFFFFFFF - getpixel(screen, x1 + 96, y)); } } } else { for (y = y1; y <= y2; y++) { num_drawn++; if (num_drawn < 10 || dont_do_xor == 0) { clipped_putpixel(screen, x1 + 96, y, 0xFFFFFFFF - getpixel(screen, x1 + 96, 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 (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; tool_avail[TOOL_NEW] = 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]; } } /* 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); } /* Build a color based on two colors and an alpha... */ static Uint8 alpha(Uint8 c1, Uint8 c2, Uint8 a) { Uint16 c, nc1, nc2, na; na = a; nc1 = c1; nc2 = c2; if (nc1 > 200) nc1 = 200; c = ((nc1 * na) / 255 + (nc2 * (255 - na)) / 255); return (Uint8) c; } /* For qsort() call in loadarbitrary()... */ static int compare_strings(char * * s1, char * * s2) { return (strcmp(*s1, *s2)); } /* 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: */ dest.x = 0; dest.y = (48 * 7) + 40 + 48 + HEIGHTOFFSET; dest.w = WINDOW_WIDTH; dest.h = WINDOW_HEIGHT - ((48 * 7) + 40 + 48 + HEIGHTOFFSET); SDL_FillRect(screen, &dest, SDL_MapRGB(screen->format, 255, 255, 255)); /* Draw tux: */ dest.x = 0; dest.y = WINDOW_HEIGHT - (img_tux[which_tux] -> h); if (dest.y < ((48 * 7) + 40 + 48 + HEIGHTOFFSET)) dest.y = ((48 * 7) + 40 + 48 + HEIGHTOFFSET); SDL_BlitSurface(img_tux[which_tux], NULL, screen, &dest); wordwrap_text(str, black, img_tux[which_tux] -> w + 5, (48 * 7) + 40 + 48 + HEIGHTOFFSET, WINDOW_WIDTH, want_right_to_left); /* Update the display: */ SDL_UpdateRect(screen, 0, (48 * 7) + 40 + 48 + HEIGHTOFFSET, WINDOW_WIDTH, WINDOW_HEIGHT - ((48 * 7) + 40 + 48 + HEIGHTOFFSET)); } 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 (want_right_to_left == 0) locale_str = strdup(gettext(str)); else locale_str = strdup(textdir(gettext(str))); /* For each UTF8 character: */ utf8_str_len = 0; utf8_str[0] = '\0'; for (i = 0; i <= strlen(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 = TTF_RenderUTF8_Blended(locale_font, 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(language) && 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 = TTF_RenderUTF8_Blended(locale_font, utf8_char, color); if (text != NULL) { if (x + text->w > right) { if (need_right_to_left(language) && 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(language) && 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(language) && 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(language) && 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!) */ if (want_right_to_left == 0) tstr = strdup(uppercase(gettext(str))); else tstr = strdup(uppercase(textdir(gettext(str)))); 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 = TTF_RenderUTF8_Blended(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(language) && 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(language) && 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(language) && 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_prefixes[language]) + 2); strcpy(snd_fname, fname); snprintf(tmp_str, sizeof(tmp_str), "_%s.wav", lang_prefixes[language]); if (strstr(snd_fname, ".png") != NULL) { strcpy(strstr(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 (strstr(snd_fname, ".png") != NULL) { strcpy(strstr(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 (strstr(txt_fname, ".png") != NULL) { strcpy(strstr(txt_fname, ".png"), ".txt"); fi = fopen(txt_fname, "r"); if (fi == NULL) { /* fprintf(stderr, "\nWarning: Couldn't open a description file:\n"); perror(txt_fname); fprintf(stderr, "\n"); */ free(txt_fname); return NULL; } free(txt_fname); 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 (strstr(buf, lang_prefixes[language]) == buf) { debug(buf + strlen(lang_prefixes[language])); if (strstr(buf + strlen(lang_prefixes[language]), ".utf8=") == buf + strlen(lang_prefixes[language])) { found = 1; debug("...FOUND!"); } } } } while (!feof(fi) && !found); fclose(fi); /* Return the string: */ if (found) { return(strdup(buf + (strlen(lang_prefixes[language])) + 6)); } else { /* No locale-specific translation; use the default (English) */ return(strdup(def_buf)); } } else { return NULL; } } /* Load a file's info: */ static info_type * loadinfo(const char * const fname) { char * dat_fname; char buf[256]; info_type inf; info_type * inf_ret; FILE * fi; /* Clear info struct first: */ inf.ratio = 1.0; inf.colorable = 0; inf.tintable = 0; inf.mirrorable = 1; inf.tintgray = 1; inf.flipable = 1; inf.tinter = TINTER_NORMAL; /* Load info! */ dat_fname = strdup(fname); if (strstr(dat_fname, ".png") != NULL) { strcpy(strstr(dat_fname, ".png"), ".dat"); fi = fopen(dat_fname, "r"); if (fi == NULL) { /* fprintf(stderr, "\nWarning: Couldn't open an info file:\n"); perror(txt_fname); fprintf(stderr, "\n"); */ free(dat_fname); return NULL; } free(dat_fname); 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) inf.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) inf.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) inf.ratio = tmp2/tmp; } else { tmp = strtod(cp,NULL); if (tmp > 0.0001 && tmp < 10000.0) inf.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, "notintgray") == 0) inf.tintgray = 0; else if (strcmp(buf, "noflip") == 0) inf.flipable = 0; } } while (!feof(fi)); fclose(fi); /* Return the info: */ inf_ret = malloc(sizeof(info_type)); /* FIXME: Check for errors! */ memcpy(inf_ret, &inf, sizeof(info_type)); return(inf_ret); } else { return NULL; } } /* Load a file's alternative image: */ static SDL_Surface * loadaltimage(const char * const fname) { char * alt_fname; SDL_Surface * s; s = NULL; alt_fname = (char *) malloc(sizeof(char) * (strlen(fname) + strlen("_mirror") + 1)); if (alt_fname != NULL) { strcpy(alt_fname, fname); if (strstr(alt_fname, ".png") != NULL) { strcpy(strstr(alt_fname, ".png"), "_mirror.png"); s = do_loadimage(alt_fname, 0); } free(alt_fname); } return s; } /* Wait for a keypress or mouse click */ static void do_wait(void) { SDL_Event event; int done, counter; done = 0; counter = 50; /* About 5 seconds */ 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 && event.button.button >= 1 && event.button.button <= 3) { done = 1; } } counter--; SDL_Delay(100); } while (!done && counter > 0); } /* Load current (if any) image: */ static void load_current(void) { SDL_Surface * tmp; char * fname; char ftmp[1024]; FILE * fi; SDL_Rect dest; /* 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 { SDL_FillRect(canvas, NULL, SDL_MapRGB(canvas->format, 255, 255, 255)); dest.x = (canvas->w - tmp->w) / 2; dest.y = (canvas->h - tmp->h) / 2; SDL_BlitSurface(tmp, NULL, canvas, &dest); SDL_FreeSurface(tmp); load_starter_id(file_id); load_starter(starter_id); if (starter_mirrored) mirror_starter(); if (starter_flipped) flip_starter(); tool_avail[TOOL_NEW] = 1; } 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 "userdata", 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 /* On Mac, Linux and other Unixes, it's in a place under our home dir.: */ #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) { SDL_Event event; SDL_Rect dest; int done, ans, w; SDL_Color black = {0, 0, 0, 0}; SDLKey key; SDLKey key_y, key_n; char keystr[200]; #ifndef NO_PROMPT_SHADOWS int i; SDL_Surface * alpha_surf; #endif /* FIXME: Move elsewhere! Or not?! */ strcpy(keystr, textdir(gettext("Yes"))); key_y = tolower(keystr[0]); strcpy(keystr, textdir(gettext("No"))); key_n = tolower(keystr[0]); 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); 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)); /* Draw the question: */ wordwrap_text(text, black, 166 + PROMPTOFFSETX, 100 + PROMPTOFFSETY, 475 + PROMPTOFFSETX, 1); /* 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; ans = 0; do { mySDL_WaitEvent(&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 && event.button.button >= 1 && event.button.button <= 3) { 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); } } } while (!done); /* FIXME: Sound effect! */ /* ... */ /* Erase question prompt: */ update_canvas(0, 0, WINDOW_WIDTH - 96 - 96, 48 * 7 + 40 + HEIGHTOFFSET); return ans; } /* Free memory and prepare to quit: */ static void cleanup(void) { int i; for (i = 0; i < MAX_STAMPS; i++) { if (txt_stamps[i]) { free(txt_stamps[i]); txt_stamps[i] = NULL; } if (inf_stamps[i]) { free(inf_stamps[i]); inf_stamps[i] = NULL; } if (state_stamps[i]) { free(state_stamps[i]); state_stamps[i] = NULL; } } free_surface_array( img_brushes, MAX_BRUSHES ); free_surface_array( img_stamps, MAX_STAMPS ); free_surface_array( img_stamps_premirror, MAX_STAMPS ); 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_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_paintcan ); free_surface( &img_sparkles ); free_surface_array( undo_bufs, NUM_UNDO_BUFS ); #ifndef LOW_QUALITY_COLOR_SELECTOR free_surface_array( img_color_btns, NUM_COLORS ); #endif free_surface_array( img_stamp_thumbs, MAX_STAMPS ); free_surface( &screen ); free_surface( &img_starter ); free_surface( &img_starter_bkgd ); free_surface( &canvas ); free_surface( &img_cur_brush ); if (font != NULL) { TTF_CloseFont(font); font = NULL; } if (small_font != NULL) { TTF_CloseFont(small_font); small_font = NULL; } if (large_font != NULL) { TTF_CloseFont(large_font); large_font = NULL; } for (i = 0; i < MAX_FONTS; i++) { if (fonts[i]) { TTF_CloseFont(fonts[i]); fonts[i] = NULL; } } #ifndef NOSOUND if (use_sound) { for (i = 0; i < NUM_SOUNDS; i++) { if (sounds[i]) { Mix_FreeChunk(sounds[i]); sounds[i] = NULL; } } for (i = 0; i < num_stamps; i++) { if (snd_stamps[i]) { Mix_FreeChunk(snd_stamps[i]); snd_stamps[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); } /* 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; #ifdef SCAN_FILL point_type pts[1024]; /* Careful! */ fpoint_type fpts_orig[1024], fpts_new[1024]; int i; int num_pts; #else int xx; #endif /* 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]; #ifdef SCAN_FILL num_pts = 0; #endif 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); } #ifdef SCAN_FILL fpts_orig[num_pts].x = (float) x2; fpts_orig[num_pts].y = (float) y2; num_pts++; #endif } if (use_brush && shape_filled[cur_shape]) { #ifdef SCAN_FILL /* FIXME: This is all broken!!! */ num_pts = clip_polygon(num_pts, fpts_orig, fpts_new); for (i = 0; i < num_pts; i++) { pts[i].x = (int) (fpts_new[i].x); pts[i].y = (int) (fpts_new[i].y); } scan_fill(num_pts, pts); #else /* 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); } #endif } /* 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(PROMPT_SAVE_OVER_TXT, PROMPT_SAVE_OVER_YES, PROMPT_SAVE_OVER_NO) == 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; 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; /* 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; } /* 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(PROMPT_QUIT_TXT, PROMPT_QUIT_YES, PROMPT_QUIT_NO); 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 static int do_open(int want_new_tool) { 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 (strstr(f->d_name, "-t.") == NULL && strstr(f->d_name, "-back.") == NULL) { if (strstr(f->d_name, FNAME_EXTENSION) != NULL #ifndef SAVE_AS_BMP /* Support legacy BMP files for load: */ || strstr(f->d_name, ".bmp") != NULL #endif ) { strcpy(fname, f->d_name); if (strstr(fname, FNAME_EXTENSION) != NULL) { strcpy(strstr(fname, FNAME_EXTENSION), ""); d_exts[num_files] = strdup(FNAME_EXTENSION); } #ifndef SAVE_AS_BMP if (strstr(fname, ".bmp") != NULL) { strcpy(strstr(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(PROMPT_OPEN_NOFILES_TXT, PROMPT_OPEN_NOFILES_YES, ""); } else { /* Let user choose an image: */ draw_tux_text(TUX_BORED, textdir(gettext_noop("Choose the picture you want, " "then click “Open”.")), 1); /* 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 || event.key.keysym.mod & KMOD_LCTRL || event.key.keysym.mod & KMOD_RCTRL) && d_places[which] != PLACE_STARTERS_DIR && !noshortcuts) { /* Delete! */ want_erase = 1; } } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button >= 1 && event.button.button <= 3) { 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(PROMPT_ERASE_TXT, PROMPT_ERASE_YES, PROMPT_ERASE_NO)) { snprintf(fname, sizeof(fname), "saved/%s%s", d_names[which], d_exts[which]); rfname = get_fname(fname); debug(rfname); if (unlink(rfname) == 0) { thumbs[which] = NULL; 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]); 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(PROMPT_OPEN_NOFILES_TXT, PROMPT_OPEN_NOFILES_YES, ""); done = 1; } } else { perror(rfname); do_prompt("CAN'T", "OK", ""); 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(PROMPT_OPEN_SAVE_TXT, PROMPT_OPEN_SAVE_YES, PROMPT_OPEN_SAVE_NO)) { 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; SDL_FillRect(canvas, NULL, SDL_MapRGB(canvas->format, 255, 255, 255)); /* FIXME: What to do when in 640x480 mode, and loading an 800x600 (or larger) image!? */ dest.x = (canvas->w - img->w) / 2; dest.y = (canvas->h - img->h) / 2; SDL_BlitSurface(img, NULL, canvas, &dest); SDL_FreeSurface(img); 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_BlitSurface(img_starter_bkgd, NULL, canvas, NULL); SDL_BlitSurface(img_starter, NULL, canvas, NULL); } reset_avail_tools(); tool_avail[TOOL_NEW] = 1; tool_avail_bak[TOOL_UNDO] = 0; tool_avail_bak[TOOL_REDO] = 0; want_new_tool = 1; } } 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); return(want_new_tool); } /* -------------- Poly Fill Stuff -------------- */ #ifdef SCANLINE_POLY_FILL static void insert_edge(edge * list, edge * edg) { edge * p, * q; debug("insert_edge()"); q = list; p = q->next; while (p != NULL) { if (edg->x_intersect < p->x_intersect) { p = NULL; } else { q = p; p = p->next; } } edg->next = q->next; q->next = edg; } static int y_next(int k, int cnt, point_type * pts) { int j; debug("y_next()"); if ((k + 1) > (cnt - 1)) j = 0; else j = k + 1; while (pts[k].y == pts[j].y) { if ((j + 1) > (cnt - 1)) j = 0; else j++; } return (pts[j].y); } static void make_edge_rec(point_type lower, point_type upper, int y_comp, edge * edg, edge * edges[]) { debug("make_edge_rec()"); edg->dx_per_scan = (float)((upper.x - lower.x) / (upper.y - lower.y)); edg->x_intersect = lower.x; if (upper.y < y_comp) edg->y_upper = upper.y - 1; else edg->y_upper = upper.y; insert_edge(edges[lower.y], edg); } static void build_edge_list(int cnt, point_type * pts, edge * edges[]) { edge * edg; point_type v1, v2; int i, y_prev; debug("build_edge_list()"); y_prev = pts[cnt - 2].y; v1.x = pts[cnt - 1].x; v1.y = pts[cnt - 1].y; for (i = 0; i < cnt; i++) { v2 = pts[i]; if (v1.y != v2.y) { edg = (edge *) malloc(sizeof(edge)); if (v1.y < v2.y) make_edge_rec(v1, v2, y_next(i, cnt, pts), edg, edges); else make_edge_rec(v2, v1, y_prev, edg, edges); } y_prev = v1.y; v1 = v2; } } static void build_active_list(int scan, edge * active, edge * edges[]) { edge * p, * q; debug("build_active_list()"); p = edges[scan]->next; while (p != NULL) { q = p->next; insert_edge(active, p); p = q; } } static void fill_scan(int scan, edge * active) { edge * p1, * p2; int i; Uint32 color; debug("fill_scan()"); color = SDL_MapRGB(canvas->format, color_hexes[cur_color][0] / 2, color_hexes[cur_color][1] / 2, color_hexes[cur_color][2] / 2); SDL_LockSurface(canvas); p1 = active->next; while (p1 != NULL) { p2 = p1->next; for (i = p1->x_intersect; i < p2->x_intersect; i++) { putpixel(canvas, i, scan, color); } p1 = p2->next; } SDL_UnlockSurface(canvas); } static void delete_after(edge * q) { edge * p; debug("delete_after()"); p = q->next; q->next = p->next; free(p); } static void update_active_list(int scan, edge * active) { edge * q, * p; debug("update_active_list()"); q = active; p = active->next; while (p != NULL) { if (scan >= p->y_upper) { p = p->next; delete_after(q); } else { p->x_intersect = p->x_intersect + p->dx_per_scan; q = p; p = p->next; } } } static void resort_active_list(edge * active) { edge * q, * p; debug("resort_active_list()"); p = active->next; active->next = NULL; while (p != NULL) { q = p->next; insert_edge(active, p); p = q; } } static void scan_fill(int cnt, point_type * pts) { /* edge * edges[48 * 7 + 40 + HEIGHTOFFSET + 5], * active; */ edge * * edges = alloca((48 * 7 + 40 + HEIGHTOFFSET + 5) * sizeof(edge*)), * active; int i, scan; debug("scan_fill()"); /* Create empty edges: */ for (i = 0; i < 48 * 7 + 40 + HEIGHTOFFSET + 5; i++) { edges[i] = (edge *) malloc(sizeof(edge)); edges[i]->next = NULL; } /* Build edge list: */ build_edge_list(cnt, pts, edges); /* Set active edge: */ active = (edge *) malloc(sizeof(edge)); active->next = NULL; /* Scan! */ for (scan = 0; scan < 48 * 7 + 40 + HEIGHTOFFSET; scan++) { build_active_list(scan, active, edges); if (active->next) { fill_scan(scan, active); update_canvas(0, scan, WINDOW_WIDTH - 96, scan); SDL_Flip(screen); SDL_Delay(10); update_active_list(scan, active); resort_active_list(active); } } /* Free edge list: */ debug("Freeing..."); for (i = 0; i < 48 * 7 + 40 + HEIGHTOFFSET; i++) { free(edges[i]); } } /* ------------- Poly clipping stuff: -------------- */ static int inside(fpoint_type p, an_edge b) { if (b == Left) { if (p.x < 0) return 0; } else if (b == Right) { if (p.x >= WINDOW_WIDTH - 96) return 0; } else if (b == Bottom) { if (p.y >= 48 * 7 + 40 + HEIGHTOFFSET) return 0; } else if (b == Top) { if (p.y < 0) return 0; } return 1; } static int cross(fpoint_type p1, fpoint_type p2, an_edge b) { if (inside(p1, b) == inside(p2, b)) return 0; else return 1; } static fpoint_type intersect(fpoint_type p1, fpoint_type p2, an_edge b) { fpoint_type ipt; float m; if (p1.x != p2.x) m = (p1.y - p2.y) / (p1.x - p2.x); else m = 1.0; if (b == Left) { ipt.x = 0; ipt.y = p2.y + (-p2.x) * m; } else if (b == Right) { ipt.x = WINDOW_WIDTH - 96 - 1; ipt.y = p2.y + ((WINDOW_WIDTH - 96 - 1) - p2.x) * m; } else if (b == Top) { ipt.y = 0; if (p1.x != p2.x) ipt.x = p2.x + (-p2.y) / m; else ipt.x = p2.x; } else if (b == Bottom) { ipt.y = (48 * 7 + 40 + HEIGHTOFFSET) - 1; if (p1.x != p2.x) ipt.x = p2.x + (((48 * 7 + 40 + HEIGHTOFFSET) - 1) - p2.y) / m; else ipt.x = p2.x; } return(ipt); } static void clip_point(fpoint_type p, an_edge b, fpoint_type * pout, int * cnt, fpoint_type * first[], fpoint_type * s) { fpoint_type ipt; if (first[b] == NULL) { first[b] = &p; } else { if (cross(p, s[b], b)) { ipt = intersect(p, s[b], b); if (b < Top) /* Should be NUM_EDGES? */ { clip_point(ipt, b + 1, pout, cnt, first, s); } else { pout[*cnt] = ipt; (*cnt)++; } } } s[b] = p; if (inside(p, b)) { if (b < Top) /* Should be NUM_EDGES? */ { clip_point(p, b + 1, pout, cnt, first, s); } else { pout[*cnt] = p; (*cnt)++; } } } static void close_clip(fpoint_type * pout, int * cnt, fpoint_type * first[], fpoint_type * s) { fpoint_type i; an_edge b; for (b = Left; b <= Top; b++) { if (cross(s[b], *first[b], b)) { i = intersect(s[b], *first[b], b); if (b < Top) { clip_point(i, b + 1, pout, cnt, first, s); } else { pout[*cnt] = i; (*cnt)++; } } } } static int clip_polygon(int n, fpoint_type * pin, fpoint_type * pout) { fpoint_type * first[NUM_EDGES] = {0, 0, 0, 0}; fpoint_type s[NUM_EDGES]; int i, cnt; cnt = 0; for (i = 0; i < n; i++) { clip_point(pin[i], Left, pout, &cnt, first, s); } close_clip(pout, &cnt, first, s); return(cnt); } #endif /* 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 } /* Determine the current language/locale, and set the language string: */ static int current_language(void) { char * loc; #ifdef WIN32 char str[128]; #endif int lang, i, found; /* Default... */ lang = LANG_EN; #ifndef WIN32 loc = setlocale(LC_MESSAGES, NULL); if (loc != NULL) { if (strstr(loc, "LC_MESSAGES") != NULL) loc = getenv("LANG"); } #else loc = getenv("LANGUAGE"); if (!loc) { loc = g_win32_getlocale(); if (loc) { snprintf(str, sizeof(str), "LANGUAGE=%s", loc); putenv(str); } } #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 */ /* (so that, e.g. "pt_BR" is recognized as "pt_br") */ if (strncasecmp(loc, lang_prefixes[i], strlen(lang_prefixes[i])) == 0) /* if (strcasecmp(loc, lang_prefixes[i]) == 0) */ { lang = i; found = 1; } } } #ifdef DEBUG printf("lang=%d\n\n", lang); sleep(10); #endif return lang; } //////////////////////////////////////////////////////////// // 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; /* Use pre-mirrored stamp image, if applicable: */ if (state_stamps[cur_stamp]->mirrored && img_stamps_premirror[cur_stamp] != NULL) { src = img_stamps_premirror[cur_stamp]; } else { src = img_stamps[cur_stamp]; } // start by scaling src = thumbnail(src, CUR_STAMP_W, CUR_STAMP_H, 0); unsigned char *alphabits = calloc(src->w+4, src->h+4); SDL_LockSurface(src); for (yy = 0; yy < src->h; yy++) { /* Compensate for flip! */ if (state_stamps[cur_stamp]->flipped) ry = src->h - 1 - yy; else ry = yy; for (xx = 0; xx < src->w; xx++) { /* Compensate for mirror! */ if (state_stamps[cur_stamp]->mirrored && img_stamps_premirror[cur_stamp] == NULL) { rx = src->w - 1 - xx; } else { rx = xx; } SDL_GetRGBA(getpixel(src, rx, ry), src->format, &dummy, &dummy, &dummy, alphabits + xx+2 + (yy+2)*(src->w+4)); } } SDL_UnlockSurface(src); int new_w = src->w+4; int new_h = src->h+4; SDL_FreeSurface(src); unsigned char *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; } } char *old_outline_data = stamp_outline_data; SDL_LockSurface(screen); // abuse this lock until I determine the correct need stamp_outline_data = outline; stamp_outline_w = new_w; stamp_outline_h = new_h; SDL_UnlockSurface(screen); 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 = 96 + 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; clipped_putpixel(screen, sx, sy, 0xFFFFFFFF - getpixel(screen, sx, sy)); } } SDL_UnlockSurface(screen); } /////////////////////////////////////////////////// /* Returns whether a particular stamp can be colored: */ static int stamp_colorable(int stamp) { if (inf_stamps[stamp] != NULL) { return inf_stamps[stamp]->colorable; } else { return 0; } } /* Returns whether a particular stamp can be tinted: */ static int stamp_tintable(int stamp) { if (inf_stamps[stamp] != NULL) { return inf_stamps[stamp]->tintable; } else { return 0; } } /* Returns whether low-saturation ('gray') parts of stamp can be tinted: */ static int stamp_tintgray(int stamp) { if (inf_stamps[stamp] != NULL) { return inf_stamps[stamp]->tintgray; } else { return 0; } } 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 show_progress_bar(void) { SDL_Rect dest, src; int x; 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); } static void do_print(void) { #if !defined(WIN32) && !defined(__BEOS__) && !defined(__APPLE__) /* Linux, Unix, etc. */ FILE * pi; pi = popen(printcommand, "w"); if (pi == NULL) { perror(printcommand); } else { if (do_png_save(pi, printcommand, canvas)) do_prompt(PROMPT_PRINT_TXT, PROMPT_PRINT_YES, ""); } #else #ifdef WIN32 /* Win32 */ char f[512]; int show = (SDL_GetModState() & KMOD_ALT) && !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 = (SDL_GetModState() & KMOD_ALT) && !fullscreen; const char* error = SurfacePrint (canvas, show); if (error) fprintf (stderr, "Cannot print: %s\n", error); else do_prompt (PROMPT_PRINT_TXT, PROMPT_PRINT_YES, ""); #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(fonts[cur_font]))) { cursor_y = ((48 * 7 + 40 + HEIGHTOFFSET) - TTF_FontHeight(fonts[cur_font])); } /* Render the text: */ if (texttool_len > 0) { str = uppercase(texttool_str); tmp_surf = TTF_RenderUTF8_Blended(fonts[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); } static void loadfonts(const char * const dir, int fatal) { DIR * d; struct dirent * f; struct stat sbuf; char fname[512]; int d_names_alloced; char * * d_names; int num_files, i; /* Open the directory: */ d = opendir(dir); if (d == NULL) { if (fatal) { fprintf(stderr, "\nError: I can't find a directory of fonts\n" "%s\n" "The system error that occurred was: %s\n", dir, strerror(errno)); cleanup(); exit(1); } else return; } /* Make some space: */ d_names_alloced = 32; d_names = (char * *) malloc(sizeof(char *) * d_names_alloced); if (d_names == NULL) { fprintf(stderr, "\nError: I can't allocate memory for directory listing!\n" "The system error that occurred was: %s\n", strerror(errno)); cleanup(); exit(1); } /* Read directory for images: */ num_files = 0; do { f = readdir(d); if (f != NULL) { d_names[num_files] = strdup(f->d_name); num_files++; if (num_files >= d_names_alloced) { d_names_alloced = d_names_alloced + 32; d_names = (char * *) realloc(d_names, sizeof(char *) * d_names_alloced); if (d_names == NULL) { fprintf(stderr, "\nError: I can't allocate memory for directory listing!\n" "The system error that occurred was: %s\n", strerror(errno)); cleanup(); exit(1); } } } } while (f != NULL); closedir(d); qsort(d_names, num_files, sizeof(char *), (int(*)(const void *, const void *))compare_strings); /* Do something with each file (load TTFs): */ for (i = 0; i < num_files && num_fonts + 3 < MAX_FONTS; i++) { /* Ignore things starting with "." (e.g., "." and ".." dirs): */ if (strstr(d_names[i], ".") != d_names[i]) { /* If it's a directory, recurse down into it: */ snprintf(fname, sizeof(fname), "%s/%s", dir, d_names[i]); debug(fname); stat(fname, &sbuf); if (strstr(d_names[i], ".ttf") != NULL) { /* If it has ".ttf" in the filename, assume we can try to load it: */ fonts[num_fonts++] = TTF_OpenFont(fname, 16); fonts[num_fonts++] = TTF_OpenFont(fname, 24); fonts[num_fonts++] = TTF_OpenFont(fname, 32); fonts[num_fonts++] = TTF_OpenFont(fname, 48); show_progress_bar(); } } free(d_names[i]); } /* Give warning if too many files were found (e.g., some not loaded): */ if (num_fonts == MAX_FONTS) { fprintf(stderr, "\nWarning: Reached maximum fonts (%d) which can be loaded.\n\n", MAX_FONTS); } } /* 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); ustr = (char *) malloc(sizeof(char) * (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); } printf(" ORIGINAL: %s\n" "UPPERCASE: %s\n\n", str, ustr); } else { ustr = strdup(str); } return(ustr); } #endif /* Return string in right-to-left mode, if necessary: */ static unsigned char * textdir(const unsigned char * const str) { unsigned char * dstr; unsigned i, j; #ifdef DEBUG printf("ORIG_DIR: %s\n", str); #endif dstr = (unsigned char *) malloc((strlen(str) + 5) * sizeof(unsigned char)); if (need_right_to_left(language)) { dstr[strlen(str)] = '\0'; for (i = 0; i < strlen(str); i++) { j = (strlen(str) - i - 1); if (str[i] < 128) /* 0xxx xxxx - 1 byte */ { dstr[j] = str[i]; } else if ((str[i] & 0xE0) == 0xC0) /* 110x xxxx - 2 bytes */ { dstr[j - 1] = str[i + 0]; dstr[j - 0] = str[i + 1]; i = i + 1; } else if ((str[i] & 0xF0) == 0xE0) /* 1110 xxxx - 3 bytes */ { dstr[j - 2] = str[i + 0]; dstr[j - 1] = str[i + 1]; dstr[j - 0] = str[i + 2]; i = i + 2; } else /* 1111 0xxx - 4 bytes */ { dstr[j - 3] = str[i + 0]; dstr[j - 2] = str[i + 1]; dstr[j - 1] = str[i + 2]; dstr[j - 0] = str[i + 3]; i = i + 3; } } } else { strcpy(dstr, str); } #ifdef DEBUG printf("L2R_DIR: %s\n", dstr); #endif return (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 { SDL_GetRGB(c1, canvas->format, &r1, &g1, &b1); SDL_GetRGB(c2, canvas->format, &r2, &g2, &b2); if (abs(r1 - r2) <= 64 && abs(g1 - g2) <= 64 && abs(b1 - b2) <= 64) return 1; else return 0; } #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; 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); /* Should "lang=" and "locale=" be here as well??? Comments welcome ... bill@newbreedsoftware.com */ /* 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, "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; } else if (strstr(str, "lang=") == str) { langstr = strdup(str + 5); #ifdef DEBUG printf("langstr set to: %s\n", langstr); #endif } 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 (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 (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); 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(fonts[cur_font], str, &w, &h); return w; } #endif static void draw_image_title(int t, int x) { SDL_Rect dest; dest.x = x; dest.y = 0; SDL_BlitSurface(img_title_on, NULL, screen, &dest); dest.x = x + (96 - img_title_names[t]->w) / 2;; dest.y = (40 - img_title_names[t]->h) / 2; SDL_BlitSurface(img_title_names[t], NULL, screen, &dest); } static int need_own_font(int l) { int i, need; need = 0; for (i = 0; lang_use_own_font[i] != -1 && need == 0; i++) { if (lang_use_own_font[i] == l) { need = 1; } } return need; } static int need_right_to_left(int l) { int i, need; need = 0; for (i = 0; lang_use_right_to_left[i] != -1 && need == 0; i++) { if (lang_use_right_to_left[i] == l) { need = 1; } } return need; } /* 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 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; SDL_Rect dest; /* Determine path to starter files: */ /* FIXME: On Windows, MacOSX, BeOS, etc. -- do it their way! */ #if defined(WIN32) || defined(__BEOS__) dirname = strdup(DATA_PREFIX "starters"); #else dirname = strdup("/usr/local/share/tuxpaint/starters"); #endif /* 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); SDL_SetAlpha(tmp_surf, 0, 0); if (img_starter != NULL) { dest.x = (canvas->w - tmp_surf->w) / 2; dest.y = (canvas->h - tmp_surf->h) / 2; SDL_BlitSurface(tmp_surf, NULL, img_starter, &dest); SDL_FreeSurface(tmp_surf); } } /* 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); if (img_starter_bkgd != NULL) { dest.x = (canvas->w - tmp_surf->w) / 2; dest.y = (canvas->h - tmp_surf->h) / 2; SDL_BlitSurface(tmp_surf, NULL, img_starter_bkgd, &dest); SDL_FreeSurface(tmp_surf); } } free(dirname); } static TTF_Font *try_alternate_font(int language) { char str[128]; char prefix[64]; char *p; strcpy(prefix, lang_prefixes[language]); if ((p = strrchr(prefix, '_')) != NULL) { *p = 0; snprintf(str, sizeof(str), "%sfonts/locale/%s.ttf", DATA_PREFIX, prefix); return TTF_OpenFont(str, 18); } return NULL; } 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; } } }