/*
 * Copyright 1997, 2001 Hj. Malthaner
 * hansjoerg.malthaner@gmx.de
 * Copyright 2010 Simutrans contributors
 * Available under the Artistic License (see license.txt)
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#include "macros.h"
#include "simtypes.h"
#include "font.h"
#include "pathes.h"
#include "simconst.h"
#include "simsys.h"
#include "simmem.h"
#include "simdebug.h"
#include "besch/bild_besch.h"
#include "unicode.h"


#ifdef _MSC_VER
#	include <io.h>
#	include <direct.h>
#	define W_OK 2
#else
#	include <sys/stat.h>
#	include <fcntl.h>
#	include <unistd.h>
#endif

// first: find out, which copy routines we may use!
#ifndef  __GNUC__
# undef USE_C
# define USE_C
# ifndef  _M_IX86
#  define ALIGN_COPY
# endif
#else
# if defined(USE_C)  ||  !defined(__i386__)
#  undef USE_C
#  define USE_C
#  if (__GNUC__>=4  &&  __GNUC_MINOR__>=2)  ||  !defined(__i386__)
#   define ALIGN_COPY
#   warning "Needs to use slower copy with GCC > 4.2.x"
#  endif
# endif
#endif

#include "simgraph.h"

// undefine for debugging the update routines
//#define DEBUG_FLUSH_BUFFER

/*
 * Hajo: RGB 1555
 */
typedef uint16 PIXVAL;


#ifdef USE_SOFTPOINTER
static int softpointer = -1;
#endif
static int standard_pointer = -1;


#ifdef USE_SOFTPOINTER
/*
 * Die Icon Leiste muss neu gezeichnet werden wenn der Mauszeiger darueber
 * schwebt
 */
int old_my = -1;
#endif


/*
 * Hajo: Current clipping rectangle
 */
static struct clip_dimension clip_rect;

// and the variables for polygon clipping

/*
 * struct to hold the information about visible area
 * at screen line y
 * associated to some clipline
 */
struct xrange {
	int sx,sy;
	KOORD_VAL y;
	bool non_convex_active;
};

#define MAX_POLY_CLIPS 6
static xrange      xranges[MAX_POLY_CLIPS];
static uint8       clip_ribi[MAX_POLY_CLIPS];
static int number_of_clips =0;
static uint8 active_ribi = 15; // set all to active


/* Flag, if we have Unicode font => do unicode (UTF8) support! *
 * @author prissi
 * @date 29.11.04
 */
static bool has_unicode = false;

static font_type large_font = { 0, 0, 0, NULL, NULL };

// needed for gui
int large_font_height = 10;

#define LIGHT_COUNT (15)
#define MAX_PLAYER_COUNT (16)

#define RGBMAPSIZE (0x8000+LIGHT_COUNT+16)

/*
 * Hajo: mapping tables for RGB 555 to actual output format
 * plus the special (player, day&night) colors appended
 *
 * 0x0000 - 0x7FFF: RGB colors
 * 0x8000 - 0x800F: Player colors
 * 0x8010 -       : Day&Night special colors
 */
static PIXVAL rgbmap_day_night[RGBMAPSIZE];


/*
 * Hajo: same as rgbmap_day_night, but allways daytime colors
 */
static PIXVAL rgbmap_all_day[RGBMAPSIZE];


/**
 * Hajo:used by pixel copy functions, is one of rgbmap_day_night
 * rgbmap_all_day
 */
static PIXVAL *rgbmap_current = 0;


/*
 * Hajo: mapping table for specialcolors (AI player colors)
 * to actual output format - day&night mode
 * 16 sets of 16 colors
 */
static PIXVAL specialcolormap_day_night[256];


/*
 * Hajo: mapping table for specialcolors (AI player colors)
 * to actual output format - all day mode
 * 16 sets of 16 colors
 */
static PIXVAL specialcolormap_all_day[256];


// offsets of first and second comany color
static uint8 player_offsets[MAX_PLAYER_COUNT][2];


/*
 * Hajo: Image map descriptor struture
 */
struct imd {
	sint16 base_x; // min x offset
	sint16 base_y; // min y offset
	sint16 base_w; //  width
	sint16 base_h; // height

	sint16 x; // current (zoomed) min x offset
	sint16 y; // current (zoomed) min y offset
	sint16 w; // current (zoomed) width
	sint16 h; // current (zoomed) height

	uint32 len; // base image data size (used for allocation purposes only)

	uint8 recode_flags; // divers flags for recoding
	uint8 player_flags; // 128 = free/needs recode, otherwise coded to this color in player_data

	PIXVAL* data; // current data, zoomed and adapted to output format RGB 555 or RGB 565

	PIXVAL* zoom_data; // zoomed original data

	PIXVAL* base_data; // original image data

	PIXVAL* player_data; // current data coded for player1 (since many building belong to him)
};

// flags for recoding
#define FLAG_ZOOMABLE (1)
#define FLAG_PLAYERCOLOR (2)
#define FLAG_REZOOM (4)
#define FLAG_NORMAL_RECODE (8)
#define FLAG_POSITION_CHANGED (16)

#define NEED_PLAYER_RECODE (128)


static KOORD_VAL disp_width  = 640;
static KOORD_VAL disp_actual_width  = 640;
static KOORD_VAL disp_height = 480;


/*
 * Image table
 */
static struct imd* images = NULL;

/*
 * Number of loaded images
 */
static image_id anz_images = 0;

/*
 * Number of allocated entries for images
 * (>= anz_images)
 */
static image_id alloc_images = 0;


/*
 * Output framebuffer
 */
static PIXVAL* textur = NULL;


/*
 * Hajo: dirty tile management strcutures
 */
#define DIRTY_TILE_SIZE 16
#define DIRTY_TILE_SHIFT 4

static unsigned char *tile_dirty = NULL;
static unsigned char *tile_dirty_old = NULL;

static int tiles_per_line = 0;
static int tile_lines = 0;
static int tile_buffer_length = 0;


static int light_level = 0;
static int night_shift = -1;


/*
 * Hajo: speical colors during daytime
 */
static const uint8 day_lights[LIGHT_COUNT*3] = {
	0x57,	0x65,	0x6F, // Dark windows, lit yellowish at night
	0x7F,	0x9B,	0xF1, // Lighter windows, lit blueish at night
	0xFF,	0xFF,	0x53, // Yellow light
	0xFF,	0x21,	0x1D, // Red light
	0x01,	0xDD,	0x01, // Green light
	0x6B,	0x6B,	0x6B, // Non-darkening grey 1 (menus)
	0x9B,	0x9B,	0x9B, // Non-darkening grey 2 (menus)
	0xB3,	0xB3,	0xB3, // non-darkening grey 3 (menus)
	0xC9,	0xC9,	0xC9, // Non-darkening grey 4 (menus)
	0xDF,	0xDF,	0xDF, // Non-darkening grey 5 (menus)
	0xE3,	0xE3,	0xFF, // Nearly white light at day, yellowish light at night
	0xC1,	0xB1,	0xD1, // Windows, lit yellow
	0x4D,	0x4D,	0x4D, // Windows, lit yellow
	0xE1,	0x00,	0xE1, // purple light for signals
	0x01, 0x01, 0xFF, // blue light
};


/*
 * Hajo: speical colors during nighttime
 */
static const uint8 night_lights[LIGHT_COUNT*3] = {
	0xD3,	0xC3,	0x80, // Dark windows, lit yellowish at night
	0x80,	0xC3,	0xD3, // Lighter windows, lit blueish at night
	0xFF,	0xFF,	0x53, // Yellow light
	0xFF,	0x21,	0x1D, // Red light
	0x01,	0xDD,	0x01, // Green light
	0x6B,	0x6B,	0x6B, // Non-darkening grey 1 (menus)
	0x9B,	0x9B,	0x9B, // Non-darkening grey 2 (menus)
	0xB3,	0xB3,	0xB3, // non-darkening grey 3 (menus)
	0xC9,	0xC9,	0xC9, // Non-darkening grey 4 (menus)
	0xDF,	0xDF,	0xDF, // Non-darkening grey 5 (menus)
	0xFF,	0xFF,	0xE3, // Nearly white light at day, yellowish light at night
	0xD3,	0xC3,	0x80, // Windows, lit yellow
	0xD3,	0xC3,	0x80, // Windows, lit yellow
	0xE1,	0x00,	0xE1, // purple light for signals
	0x01, 0x01, 0xFF, // blue light
};


// the players colors and colors for simple drawing operations
// each eight colors are corresponding to a player color
static const uint8 special_pal[224*3]=
{
	36, 75, 103,
	57, 94, 124,
	76, 113, 145,
	96, 132, 167,
	116, 151, 189,
	136, 171, 211,
	156, 190, 233,
	176, 210, 255,
	88, 88, 88,
	107, 107, 107,
	125, 125, 125,
	144, 144, 144,
	162, 162, 162,
	181, 181, 181,
	200, 200, 200,
	219, 219, 219,
	17, 55, 133,
	27, 71, 150,
	37, 86, 167,
	48, 102, 185,
	58, 117, 202,
	69, 133, 220,
	79, 149, 237,
	90, 165, 255,
	123, 88, 3,
	142, 111, 4,
	161, 134, 5,
	180, 157, 7,
	198, 180, 8,
	217, 203, 10,
	236, 226, 11,
	255, 249, 13,
	86, 32, 14,
	110, 40, 16,
	134, 48, 18,
	158, 57, 20,
	182, 65, 22,
	206, 74, 24,
	230, 82, 26,
	255, 91, 28,
	34, 59, 10,
	44, 80, 14,
	53, 101, 18,
	63, 122, 22,
	77, 143, 29,
	92, 164, 37,
	106, 185, 44,
	121, 207, 52,
	0, 86, 78,
	0, 108, 98,
	0, 130, 118,
	0, 152, 138,
	0, 174, 158,
	0, 196, 178,
	0, 218, 198,
	0, 241, 219,
	74, 7, 122,
	95, 21, 139,
	116, 37, 156,
	138, 53, 173,
	160, 69, 191,
	181, 85, 208,
	203, 101, 225,
	225, 117, 243,
	59, 41, 0,
	83, 55, 0,
	107, 69, 0,
	131, 84, 0,
	155, 98, 0,
	179, 113, 0,
	203, 128, 0,
	227, 143, 0,
	87, 0, 43,
	111, 11, 69,
	135, 28, 92,
	159, 45, 115,
	183, 62, 138,
	230, 74, 174,
	245, 121, 194,
	255, 156, 209,
	20, 48, 10,
	44, 74, 28,
	68, 99, 45,
	93, 124, 62,
	118, 149, 79,
	143, 174, 96,
	168, 199, 113,
	193, 225, 130,
	54, 19, 29,
	82, 44, 44,
	110, 69, 58,
	139, 95, 72,
	168, 121, 86,
	197, 147, 101,
	226, 173, 115,
	255, 199, 130,
	8, 11, 100,
	14, 22, 116,
	20, 33, 139,
	26, 44, 162,
	41, 74, 185,
	57, 104, 208,
	76, 132, 231,
	96, 160, 255,
	43, 30, 46,
	68, 50, 85,
	93, 70, 110,
	118, 91, 130,
	143, 111, 170,
	168, 132, 190,
	193, 153, 210,
	219, 174, 230,
	63, 18, 12,
	90, 38, 30,
	117, 58, 42,
	145, 78, 55,
	172, 98, 67,
	200, 118, 80,
	227, 138, 92,
	255, 159, 105,
	11, 68, 30,
	33, 94, 56,
	54, 120, 81,
	76, 147, 106,
	98, 174, 131,
	120, 201, 156,
	142, 228, 181,
	164, 255, 207,
	64, 0, 0,
	96, 0, 0,
	128, 0, 0,
	192, 0, 0,
	255, 0, 0,
	255, 64, 64,
	255, 96, 96,
	255, 128, 128,
	0, 128, 0,
	0, 196, 0,
	0, 225, 0,
	0, 240, 0,
	0, 255, 0,
	64, 255, 64,
	94, 255, 94,
	128, 255, 128,
	0, 0, 128,
	0, 0, 192,
	0, 0, 224,
	0, 0, 255,
	0, 64, 255,
	0, 94, 255,
	0, 106, 255,
	0, 128, 255,
	128, 64, 0,
	193, 97, 0,
	215, 107, 0,
	255, 128, 0,
	255, 128, 0,
	255, 149, 43,
	255, 170, 85,
	255, 193, 132,
	8, 52, 0,
	16, 64, 0,
	32, 80, 4,
	48, 96, 4,
	64, 112, 12,
	84, 132, 20,
	104, 148, 28,
	128, 168, 44,
	164, 164, 0,
	193, 193, 0,
	215, 215, 0,
	255, 255, 0,
	255, 255, 32,
	255, 255, 64,
	255, 255, 128,
	255, 255, 172,
	32, 4, 0,
	64, 20, 8,
	84, 28, 16,
	108, 44, 28,
	128, 56, 40,
	148, 72, 56,
	168, 92, 76,
	184, 108, 88,
	64, 0, 0,
	96, 8, 0,
	112, 16, 0,
	120, 32, 8,
	138, 64, 16,
	156, 72, 32,
	174, 96, 48,
	192, 128, 64,
	32, 32, 0,
	64, 64, 0,
	96, 96, 0,
	128, 128, 0,
	144, 144, 0,
	172, 172, 0,
	192, 192, 0,
	224, 224, 0,
	64, 96, 8,
	80, 108, 32,
	96, 120, 48,
	112, 144, 56,
	128, 172, 64,
	150, 210, 68,
	172, 238, 80,
	192, 255, 96,
	32, 32, 32,
	48, 48, 48,
	64, 64, 64,
	80, 80, 80,
	96, 96, 96,
	172, 172, 172,
	236, 236, 236,
	255, 255, 255,
	41, 41, 54,
	60, 45, 70,
	75, 62, 108,
	95, 77, 136,
	113, 105, 150,
	135, 120, 176,
	165, 145, 218,
	198, 191, 232,
};


/*
 * Hajo: tile raster width
 */
KOORD_VAL tile_raster_width = 16;	// zoomed
KOORD_VAL base_tile_raster_width = 16;	// original

// Knightly : variables for storing currently used image procedure set and tile raster width
display_image_proc display_normal = NULL;
display_image_proc display_color = NULL;
display_blend_proc display_blend = NULL;
signed short current_tile_raster_width = 0;



/*
 * Hajo: Zoom factor
 */
#define MAX_ZOOM_FACTOR (9)
static uint32 zoom_factor = 3;
static sint32 zoom_num[MAX_ZOOM_FACTOR+1] = { 2, 3, 4, 1, 3, 5, 1, 3, 1, 1 };
static sint32 zoom_den[MAX_ZOOM_FACTOR+1] = { 1, 2, 3, 1, 4, 8, 2, 8, 4, 8 };



/* changes the raster width after loading */
KOORD_VAL display_set_base_raster_width(KOORD_VAL new_raster)
{
	KOORD_VAL old = base_tile_raster_width;
	base_tile_raster_width = new_raster;
	tile_raster_width = (new_raster *  zoom_num[zoom_factor]) / zoom_den[zoom_factor];
	return old;
}


// ----------------------------------- clipping routines ------------------------------------------


sint16 display_get_width(void)
{
	return disp_actual_width;
}


// only use, if you are really really sure!
void display_set_actual_width(KOORD_VAL w)
{
	disp_actual_width = w;
}


sint16 display_get_height(void)
{
	return disp_height;
}


sint16 display_set_height(KOORD_VAL h)
{
	sint16 old = disp_height;
	disp_height = h;
	return old;
}


/**
 * this sets width < 0 if the range is out of bounds
 * so check the value after calling and before displaying
 * @author Hj. Malthaner
 */
static int clip_wh(KOORD_VAL *x, KOORD_VAL *width, const KOORD_VAL min_width, const KOORD_VAL max_width)
{
	if (*width <= 0) {
		*width = 0;
		return 0;
	}
	if (*x + *width > max_width) {
		*width = max_width - *x;
	}
	if (*x < min_width) {
		const KOORD_VAL xoff = min_width - *x;

		*width += *x-min_width;
		*x = min_width;

		return xoff;
	}
	return 0;
}


/**
 * places x and w within bounds left and right
 * if nothing to show, returns FALSE
 * @author Niels Roest
 */
static int clip_lr(KOORD_VAL *x, KOORD_VAL *w, const KOORD_VAL left, const KOORD_VAL right)
{
	const KOORD_VAL l = *x;      // leftmost pixel
	const sint32 r = (sint32)*x + (sint32)*w; // rightmost pixel

	if (*w <= 0 || l >= right || r <= left) {
		*w = 0;
		return FALSE;
	}

	// there is something to show.
	if (l < left) {
		*w -= left - l;
		*x = left;
	}
	if (r > right) {
		*w -= (KOORD_VAL)(r - right);
	}
	return *w > 0;
}


/**
 * Ermittelt Clipping Rechteck
 * @author Hj. Malthaner
 */
struct clip_dimension display_get_clip_wh(void)
{
	return clip_rect;
}


/**
 * Setzt Clipping Rechteck
 * @author Hj. Malthaner
 *
 * here, a pixel at coordinate xp is displayed if
 *  clip. x <= xp < clip.xx
 * the right-most pixel of an image located at xp with width w is displayed if
 *  clip.x < xp+w <= clip.xx
 * analogously for the y coordinate
 */
void display_set_clip_wh(KOORD_VAL x, KOORD_VAL y, KOORD_VAL w, KOORD_VAL h)
{
	clip_wh(&x, &w, 0, disp_width);
	clip_wh(&y, &h, 0, disp_height);

	clip_rect.x = x;
	clip_rect.y = y;
	clip_rect.w = w;
	clip_rect.h = h;

	clip_rect.xx = x + w; // watch out, clips to KOORD_VAL max
	clip_rect.yy = y + h; // watch out, clips to KOORD_VAL max
}



class clip_line_t {
private:
	// line from (x0,y0) to (x1 y1)
	// clip (do not draw) everything right from the ray (x0,y0)->(x1,y1)
	// pixels on the ray are not drawn
	// (not_convex) if y0>=y1 then clip along the path (x0,-inf)->(x0,y0)->(x1,y1)
	// (not_convex) if y0<y1  then clip along the path (x0,y0)->(x1,y1)->(x1,+inf)
	int x0,y0;
	int dx,dy,sdy,sdx;
	int inc;
	bool non_convex;

public:
	void clip_from_to(int x0_,int y0_, int x1, int y1, bool non_convex_) {
		x0 = x0_;
		dx = x1-x0;
		y0 = y0_;
		dy = y1-y0;
		non_convex = non_convex_;
		int steps = (abs(dx) > abs(dy) ? abs(dx) : abs(dy));
		if (steps == 0) {
			return;
		}
		sdx = (dx << 16) / steps;
		sdy = (dy << 16) / steps;
		// to stay right from the line
		// left border: xmin <= x
		// right border: x < xmax
		if (dy>0) {
			if (dy > dx) {
				inc = 1 << 16;
			}
			else {
				inc = (dx << 16)/dy;
			}
		}
		else if (dy<0) {
			if (dy < dx) {
				inc = 0; // (+1)-1 << 16;
			}
			else {
				inc = (1 << 16) - (dx << 16) / dy;
			}
		}
	}

	// clip if
	// ( x-x0) . (  y1-y0 )
	// ( y-y0) . (-(x1-x0)) < 0
	// -- initialize the clipping
	//    has to be called before image will be drawn
	//    return interval for x coordinate
	inline void get_x_range(KOORD_VAL y, xrange &r) const {
		// do everything for the previous row
		y--;
		r.y = y;
		r.non_convex_active = false;
		if (non_convex  &&  y<y0  &&  y<(y0+dy)) {
			r.non_convex_active = true;
		}
		else if (dy != 0) {
			// init Bresenham algorithm
			const int t = ((y-y0) << 16) / sdy;
			// sx >> 16 = x
			// sy >> 16 = y
			r.sx = t * sdx + inc + (x0 << 16);
			r.sy = t * sdy + (y0 << 16);
		}
	}

	// -- step one line down, return interval for x coordinate
	inline void inc_y(xrange &r, int &xmin, int &xmax) const {
		r.y ++;
		// switch between clip vertical and along ray
		if (r.non_convex_active) {
			if (r.y==min(y0,y0+dy)) {
				r.non_convex_active = false;
				if (dy != 0) {
					// init Bresenham algorithm
					const int t = ((r.y-y0) << 16) / sdy;
					// sx >> 16 = x
					// sy >> 16 = y
					r.sx = t * sdx + inc + (x0 << 16);
					r.sy = t * sdy + (y0 << 16);
					if (dy > 0) {
						const int r_xmin = r.sx >> 16;
						if (xmin < r_xmin) xmin = r_xmin;
					}
					else {
						const int r_xmax = r.sx >> 16;
						if (xmax > r_xmax) xmax = r_xmax;
					}
				}
			}
			else {
				if (dy<0) {
					const int r_xmax = x0+dx;
					if (xmax > r_xmax) xmax = r_xmax;
				}
				else {
					const int r_xmin = x0+1;
					if (xmin < r_xmin) xmin = r_xmin;
				}
			}
		}
		// go along the ray, Bresenham
		else if (dy!=0) {
			if (dy > 0) {
				do {
					r.sx += sdx;
					r.sy += sdy;
				} while ((r.sy >> 16) < r.y);
				const int r_xmin = r.sx >> 16;
				if (xmin < r_xmin) xmin = r_xmin;
			}
			else {
				do {
					r.sx -= sdx;
					r.sy -= sdy;
				} while ((r.sy >> 16) < r.y);
				const int r_xmax = r.sx >> 16;
				if (xmax > r_xmax) xmax = r_xmax;
			}
		}
		// horicontal clip
		else {
			const bool clip = dx*(r.y-y0)>0;
			if (clip) {
				// invisible row
				xmin = +1;
				xmax = -1;
			}
		}
	}
};


static clip_line_t poly_clips[MAX_POLY_CLIPS];


/*
 * Add clipping line through (x0,y0) and (x1,y1)
 * with associated ribi
 * if ribi & 16 then non-convex clipping.
 */
void add_poly_clip(int x0,int y0, int x1, int y1, int ribi)
{
	if (number_of_clips < MAX_POLY_CLIPS) {
		poly_clips[number_of_clips].clip_from_to(x0,y0,x1,y1,ribi&16);
		clip_ribi[number_of_clips] = ribi&15;
		number_of_clips++;
	}
}


/*
 * Clears all clipping lines
 */
void clear_all_poly_clip()
{
	number_of_clips = 0;
	active_ribi = 15; // set all to active
}


/*
 * Activates clipping lines associated with ribi
 * ie if clip_ribi[i] & active_ribi
 */
void activate_ribi_clip(int ribi)
{
	active_ribi = ribi;
}


/*
 * Initialize clipping region for image starting at screen line y
 */
static inline void init_ranges(int y)
{
	for (uint8 i=0; i<number_of_clips; i++) {
		if (clip_ribi[i] & active_ribi) {
			poly_clips[i].get_x_range(y,xranges[i]);
		}
	}
}


/*
 * Returns left/right border of visible area
 * Computes l/r border for the next line (ie y+1)
 * takes also clipping rectangle into account
 */
inline void get_xrange_and_step_y(int &xmin, int &xmax)
{
	xmin = clip_rect.x;
	xmax = clip_rect.xx;
	for (uint8 i=0; i<number_of_clips; i++) {
		if (clip_ribi[i] & active_ribi) {
			poly_clips[i].inc_y(xranges[i], xmin, xmax);
		}
	}
}


// ------------------------------ dirty tile stuff --------------------------------


/*
 * simutrans keeps a list of dirty areas, i.e. places that received new graphics
 * and must be copied to the screen after an update
 */
static inline void mark_tile_dirty(const int x, const int y)
{
	const int bit = x + y * tiles_per_line;
#if 0
	assert(bit / 8 < tile_buffer_length);
#endif
	tile_dirty[bit >> 3] |= 1 << (bit & 7);
}


static inline void mark_tiles_dirty(const int x1, const int x2, const int y)
{
	int bit = y * tiles_per_line + x1;
	const int end = bit + x2 - x1;

	do {
#if 0
		assert(bit / 8 < tile_buffer_length);
#endif

		tile_dirty[bit >> 3] |= 1 << (bit & 7);
	} while (++bit <= end);
}


static inline int is_tile_dirty(const int x, const int y)
{
	const int bit = x + y * tiles_per_line;
	const int bita = bit >> 3;
	const int bitb = 1 << (bit & 7);

	return (tile_dirty[bita] | tile_dirty_old[bita]) & bitb;
}


/**
 * Markiert ein Tile as schmutzig, ohne Clipping
 * @author Hj. Malthaner
 */
static void mark_rect_dirty_nc(KOORD_VAL x1, KOORD_VAL y1, KOORD_VAL x2, KOORD_VAL y2)
{
	// floor to tile size
	x1 >>= DIRTY_TILE_SHIFT;
	y1 >>= DIRTY_TILE_SHIFT;
	x2 >>= DIRTY_TILE_SHIFT;
	y2 >>= DIRTY_TILE_SHIFT;

#if 0
	assert(x1 >= 0);
	assert(x1 < tiles_per_line);
	assert(y1 >= 0);
	assert(y1 < tile_lines);
	assert(x2 >= 0);
	assert(x2 < tiles_per_line);
	assert(y2 >= 0);
	assert(y2 < tile_lines);
#endif

	for (; y1 <= y2; y1++) {
		mark_tiles_dirty(x1, x2, y1);
	}
}


/**
 * Markiert ein Tile as schmutzig, mit Clipping
 * @author Hj. Malthaner
 */
void mark_rect_dirty_wc(KOORD_VAL x1, KOORD_VAL y1, KOORD_VAL x2, KOORD_VAL y2)
{
	// inside display?
	if (x2 >= 0 && y2 >= 0 && x1 < disp_width && y1 < disp_height) {
		if (x1 < 0) {
			x1 = 0;
		}
		if (y1 < 0) {
			y1 = 0;
		}
		if (x2 >= disp_width) {
			x2 = disp_width  - 1;
		}
		if (y2 >= disp_height) {
			y2 = disp_height - 1;
		}
		mark_rect_dirty_nc(x1, y1, x2, y2);
	}
}


/**
 * the area of this image need update
 * @author Hj. Malthaner
 */
void display_mark_img_dirty(unsigned bild, KOORD_VAL xp, KOORD_VAL yp)
{
	if (bild < anz_images) {
		mark_rect_dirty_wc(
			xp + images[bild].x,
			yp + images[bild].y,
			xp + images[bild].x + images[bild].w - 1,
			yp + images[bild].y + images[bild].h - 1
		);
	}
}


// ------------------------- rendering images for display --------------------------------

/*
 * Simutrans caches two player colored images, to allow faster drawing of them
 * They are derived from a base image, which may need zooming too
 */

/**
 * Rezooms all images
 * @author Hj. Malthaner
 */
static void rezoom(void)
{
	uint16 n;

	for (n = 0; n < anz_images; n++) {
		if((images[n].recode_flags & FLAG_ZOOMABLE) != 0 && images[n].base_h > 0) {
			images[n].recode_flags |= FLAG_REZOOM;
		}
		images[n].recode_flags |= FLAG_NORMAL_RECODE;
		images[n].player_flags = NEED_PLAYER_RECODE;	// color will be set next time
	}
}


void set_zoom_factor(int z)
{
	zoom_factor = z;
	tile_raster_width = (base_tile_raster_width * zoom_num[zoom_factor]) / zoom_den[zoom_factor];
	fprintf(stderr, "set_zoom_factor() : set %d (%i/%i)\n", zoom_factor, zoom_num[zoom_factor], zoom_den[zoom_factor] );
	rezoom();
}



int zoom_factor_up()
{
	// zoom out, if size permits
	if(  zoom_factor>0  ) {
		set_zoom_factor( zoom_factor-1 );
		return true;
	}
	return false;
}


int zoom_factor_down()
{
	if (zoom_factor<MAX_ZOOM_FACTOR) {
		set_zoom_factor( zoom_factor+1 );
		return true;
	}
	return false;
}


static uint8 player_night=0xFF;
static uint8 player_day=0xFF;
static void activate_player_color(sint8 player_nr, bool daynight)
{
	// cahces the last settings
	if(!daynight) {
		if(player_day!=player_nr) {
			int i;
			player_day = player_nr;
			for(i=0;  i<8;  i++  ) {
				rgbmap_all_day[0x8000+i] = specialcolormap_all_day[player_offsets[player_day][0]+i];
				rgbmap_all_day[0x8008+i] = specialcolormap_all_day[player_offsets[player_day][1]+i];
			}
		}
		rgbmap_current = rgbmap_all_day;
	}
	else {
		// changing colortable
		if(player_night!=player_nr) {
			int i;
			player_night = player_nr;
			for(i=0;  i<8;  i++  ) {
				rgbmap_day_night[0x8000+i] = specialcolormap_day_night[player_offsets[player_night][0]+i];
				rgbmap_day_night[0x8008+i] = specialcolormap_day_night[player_offsets[player_night][1]+i];
			}
		}
		rgbmap_current = rgbmap_day_night;
	}
}


/**
 * Convert image data to actual output data
 * @author Hj. Malthaner
 */
static void recode(void)
{
	unsigned n;

	for (n = 0; n < anz_images; n++) {
		// recode images only if the recoded image is needed
		images[n].recode_flags |= FLAG_NORMAL_RECODE;
		images[n].player_flags = NEED_PLAYER_RECODE;
	}
}


/**
 * Convert a certain image data to actual output data
 * @author Hj. Malthaner
 */
static void recode_img_src_target(KOORD_VAL h, PIXVAL *src, PIXVAL *target)
{
	if (h > 0) {
		do {
			uint16 runlen = *target++ = *src++;

			// eine Zeile dekodieren
			do {
				// clear run is always ok

				runlen = *target++ = *src++;
				while (runlen--) {
					// now just convert the color pixels
					*target++ = rgbmap_day_night[*src++];
				}
			} while ((runlen = *target++ = *src++));
		} while (--h);
	}
}


/**
 * Handles the conversion of an image to the output color
 * @author prissi
 */
static void recode_normal_img(const unsigned int n)
{
	PIXVAL *src = images[n].zoom_data != NULL ? images[n].zoom_data : images[n].base_data;

	if (images[n].data == NULL) {
		images[n].data = MALLOCN(PIXVAL, images[n].len);
	}
	// now do normal recode
	activate_player_color( 0, true );
	recode_img_src_target(images[n].h, src, images[n].data);
	images[n].recode_flags &= ~FLAG_NORMAL_RECODE;
}


#ifdef NEED_PLAYER_RECODE

/**
 * Convert a certain image data to actual output data for a certain player
 * @author prissi
 */
static void recode_img_src_target_color(KOORD_VAL h, PIXVAL *src, PIXVAL *target)
{
	if (h > 0) {
		do {
			uint16 runlen = *target++ = *src++;
			// eine Zeile dekodieren

			do {
				// clear run is always ok

				runlen = *target++ = *src++;
				// now just convert the color pixels
				while (runlen--) {
					*target++ = rgbmap_day_night[*src++];
				}
				// next clea run or zero = end
			} while ((runlen = *target++ = *src++));
		} while (--h);
	}
}


/**
 * Handles the conversion of an image to the output color
 * @author prissi
 */
static void recode_color_img(const unsigned int n, const unsigned char player_nr)
{
	PIXVAL *src = images[n].zoom_data != NULL ? images[n].zoom_data : images[n].base_data;

	images[n].player_flags = player_nr;
	if (images[n].player_data == NULL) {
		images[n].player_data = MALLOCN(PIXVAL, images[n].len);
	}
	// contains now the player color ...
	activate_player_color( player_nr, true );
	recode_img_src_target_color(images[n].h, src, images[n].player_data );
}

#endif


#define SumSubpixel(p) \
	if(*(p)<255  &&  valid<255) { \
	if(*(p)==1) { valid = 255; r = g = b = 0; } else { valid ++; } /* mark special colors */\
		r += (p)[1]; \
		g += (p)[2]; \
		b += (p)[3]; \
	}


/**
 * Convert base image data to actual image size
 * Uses averages of all sampled points to get the "real" value
 * Blurs a bit
 * @author prissi
 */
static void rezoom_img(const image_id n)
{
	// Hajo: may this image be zoomed
	if (n < anz_images && images[n].base_h > 0) {
		// we may need night conversion afterwards
		images[n].recode_flags &= ~FLAG_REZOOM;
		images[n].recode_flags |= FLAG_NORMAL_RECODE;
		images[n].player_flags = NEED_PLAYER_RECODE;

		//  we recalculate the len (since it may be larger than before)
		// thus we have to free the old caches
		if (images[n].zoom_data != NULL) {
			guarded_free(images[n].zoom_data);
			images[n].zoom_data = NULL;
		}
		if (images[n].data != NULL) {
			guarded_free(images[n].data);
			images[n].data = NULL;
		}
		if (images[n].player_data != NULL) {
			guarded_free(images[n].player_data);
			images[n].player_data = NULL;
		}

		// just restore original size?
		if (tile_raster_width == base_tile_raster_width  ||  (images[n].recode_flags&FLAG_ZOOMABLE)==0) {

			// this we can do be a simple copy ...
			images[n].x = images[n].base_x;
			images[n].w = images[n].base_w;
			images[n].y = images[n].base_y;
			images[n].h = images[n].base_h;
			{
				// recalculate length
				sint16 h = images[n].base_h;
				PIXVAL *sp = images[n].base_data;

				while(h-->0) {
					do {
						// clear run + colored run + next clear run
						sp++;
						sp += *sp + 1;
					} while (*sp);
					sp++;
				}
				images[n].len = (uint32)(size_t)(sp-images[n].base_data);
			}
			return;
		}

		// now we want to downsize the image
		// just divede the sizes
		images[n].x = (images[n].base_x*zoom_num[zoom_factor]) / zoom_den[zoom_factor];
		images[n].y = (images[n].base_y*zoom_num[zoom_factor]) / zoom_den[zoom_factor];
		images[n].w = (images[n].base_w*zoom_num[zoom_factor]) / zoom_den[zoom_factor];
		images[n].h = (images[n].base_h*zoom_num[zoom_factor]) / zoom_den[zoom_factor];

		if (images[n].h > 0  &&  images[n].w > 0) {
			// just recalculate the image in the new size
			static uint8 *baseimage = NULL;
			static uint32 size = 0;
			static PIXVAL *baseimage2 = NULL;
			PIXVAL *src = images[n].base_data;
			PIXVAL *dest = NULL;
			// embed the baseimage in an image with margin ~ remainder
			const sint16 x_rem = (images[n].base_x*zoom_num[zoom_factor]) % zoom_den[zoom_factor];
			const sint16 y_rem = (images[n].base_y*zoom_num[zoom_factor]) % zoom_den[zoom_factor];
			const sint16 xl_margin = max( x_rem, 0);
			const sint16 xr_margin = max(-x_rem, 0);
			const sint16 yl_margin = max( y_rem, 0);
			const sint16 yr_margin = max(-y_rem, 0);
			// baseimage top-left  corner is at (xl_margin, yl_margin)
			// ...       low-right corner is at (xr_margin, yr_margin)


			sint32 orgzoomwidth = ((images[n].base_w + zoom_den[zoom_factor] - 1 ) / zoom_den[zoom_factor]) * zoom_den[zoom_factor];
			sint32 newzoomwidth = (orgzoomwidth*zoom_num[zoom_factor])/zoom_den[zoom_factor];
			sint32 orgzoomheight = ((images[n].base_h + zoom_den[zoom_factor] - 1 ) / zoom_den[zoom_factor]) * zoom_den[zoom_factor];
			sint32 newzoomheight = (orgzoomheight*zoom_num[zoom_factor])/zoom_den[zoom_factor];

			// we will upack, resample, pack it

			// thus the unpack buffer must at least fit the window => find out maximum size
			size_t new_size = newzoomwidth*(newzoomheight+6)*sizeof(PIXVAL);
			size_t unpack_size = (xl_margin+orgzoomwidth+xr_margin)*(yl_margin+orgzoomheight+yr_margin)*4;
			if( unpack_size > new_size ) {
				new_size = unpack_size;
			}
			if(size < new_size) {
				free( baseimage );
				free( baseimage2 );
				size = new_size;
				baseimage  = MALLOCN( uint8, size );
				baseimage2 = (PIXVAL *)MALLOCN( uint8, size );
			}
			memset( baseimage, 255, size ); // fill with invalid data to mark transparent regions

			// index of top-left corner
			uint32 baseoff = 4*(yl_margin*(xl_margin+orgzoomwidth+xr_margin)+xl_margin);
			sint32 basewidth = xl_margin+orgzoomwidth+xr_margin;

			// now: unpack the image
			for (sint32 y = 0; y < images[n].base_h; ++y) {
				uint16 runlen;
				uint8 *p = baseimage + baseoff + y*(basewidth*4);

				// decode line
				runlen = *src++;
				do {
					// clear run
					p += runlen*4;
					// color pixel
					runlen = *src++;
					while (runlen--) {
						// get rgb components
						PIXVAL s = *src++;
						*p++ = (s>>15);
						*p++ = (s & 31);
						s >>= 5;
						*p++ = (s & 31);
						s >>= 5;
						*p++ = (s & 31);
					}
					runlen = *src++;
				} while (runlen!=0);
			}


			// now we have the image, we do a repack then
			dest = baseimage2;
			switch(zoom_den[zoom_factor]) {
				case 1:
					assert(zoom_num[zoom_factor]==2);
					// we just filter (diagonal filter) together with the upscaling
					for(  sint16 y=0;  y<orgzoomheight;  y++  ) {
						uint8 *p1 = baseimage + baseoff + y*(basewidth*4);
						uint8 *p2 = p1 + ((y+1<orgzoomheight) ? basewidth*4 : 0);
						for(  sint16 x=0;  x<orgzoomwidth;  x++  ) {
							uint8 *px1=p1+(x*4);
							PIXVAL c1 = px1[0]==255 ? 0x73FE : (((px1[0]==1 ? 0x8000 : 0 ) | px1[1]) + (((uint16)px1[2])<<5) + (((uint16)px1[3])<<10));
							// now set the pixel ...
							dest[x*2] = c1;
							dest[x*2+1] = c1;
							dest[x*2+newzoomwidth] = c1;
							dest[x*2+newzoomwidth+1] = c1;
							if(  px1[0]<255  ) {
								// diagonal filter only for non-transparent ones ...
								if(  x>0  &&  *(uint32 *)(px1-4)==*(uint32 *)(p2+x*4)  ) {
									// take this instead
									px1=p1+(x*4)-4;
									dest[x*2+newzoomwidth] = px1[0]==255 ? 0x73FE : (((px1[0]==1 ? 0x8000 : 0 ) | px1[1]) + (((uint16)px1[2])<<5) + (((uint16)px1[3])<<10));
								}
								else {
									dest[x*2+newzoomwidth] = c1;
								}
								px1=p1+(x*4);
								if(  x+1<orgzoomwidth  &&  *(uint32 *)(px1+4)==*(uint32 *)(p2+x*4)  ) {
									// take this instead
									px1=p1+(x*4)+4;
									dest[x*2+newzoomwidth+1] = px1[0]==255 ? 0x73FE : (((px1[0]==1 ? 0x8000 : 0 ) | px1[1]) + (((uint16)px1[2])<<5) + (((uint16)px1[3])<<10));
								}
								else {
									dest[x*2+newzoomwidth+1] = c1;
								}
							}
						}
						// skip one line
						dest += 2*newzoomwidth;
					}
					break;
				case 2:
					for(  sint16 y=0;  y<newzoomheight;  y++  ) {
						uint8 *p1 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+0-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p2 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+1-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						for(  sint16 x=0;  x<newzoomwidth;  x++  ) {
							uint8 valid=0;
							uint8 r=0,g=0,b=0;
							sint16 xreal1 = ((x*zoom_den[zoom_factor]+0-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal2 = ((x*zoom_den[zoom_factor]+1-x_rem)/zoom_num[zoom_factor])*4;
							SumSubpixel(p1+xreal1);
							SumSubpixel(p1+xreal2);
							SumSubpixel(p2+xreal1);
							SumSubpixel(p2+xreal2);
							if(valid==0) {
								*dest++ = 0x73FE;
							}
							else if(valid==255) {
								*dest++ = (0x8000 | r) + (((uint16)g)<<5) + (((uint16)b)<<10);
							}
							else {
								*dest++ = (r/valid) + (((uint16)(g/valid))<<5) + (((uint16)(b/valid))<<10);
							}
						}
					}
					break;
				case 3:
					for(  sint16 y=0;  y<newzoomheight;  y++  ) {
						uint8 *p1 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+0-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p2 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+1-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p3 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+2-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						for(  sint16 x=0;  x<newzoomwidth;  x++  ) {
							uint8 valid=0;
							uint16 r=0,g=0,b=0;
							sint16 xreal1 = ((x*zoom_den[zoom_factor]+0-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal2 = ((x*zoom_den[zoom_factor]+1-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal3 = ((x*zoom_den[zoom_factor]+2-x_rem)/zoom_num[zoom_factor])*4;
							SumSubpixel(p1+xreal1);
							SumSubpixel(p1+xreal2);
							SumSubpixel(p1+xreal3);
							SumSubpixel(p2+xreal1);
							SumSubpixel(p2+xreal2);
							SumSubpixel(p2+xreal3);
							SumSubpixel(p3+xreal1);
							SumSubpixel(p3+xreal2);
							SumSubpixel(p3+xreal3);
							if(valid==0) {
								*dest++ = 0x73FE;
							}
							else if(valid==255) {
								*dest++ = (0x8000 | r) + (((uint16)g)<<5) + (((uint16)b)<<10);
							}
							else {
								*dest++ = (r/valid) | (((uint16)(g/valid))<<5) | (((uint16)(b/valid))<<10);
							}
						}
					}
					break;
				case 4:
					for(  sint16 y=0;  y<newzoomheight;  y++  ) {
						uint8 *p1 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+0-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p2 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+1-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p3 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+2-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p4 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+3-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						for(  sint16 x=0;  x<newzoomwidth;  x++  ) {
							uint8 valid=0;
							uint16 r=0,g=0,b=0;
							sint16 xreal1 = ((x*zoom_den[zoom_factor]+0-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal2 = ((x*zoom_den[zoom_factor]+1-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal3 = ((x*zoom_den[zoom_factor]+2-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal4 = ((x*zoom_den[zoom_factor]+3-x_rem)/zoom_num[zoom_factor])*4;
							SumSubpixel(p1+xreal1);
							SumSubpixel(p1+xreal2);
							SumSubpixel(p1+xreal3);
							SumSubpixel(p1+xreal4);
							SumSubpixel(p2+xreal1);
							SumSubpixel(p2+xreal2);
							SumSubpixel(p2+xreal3);
							SumSubpixel(p2+xreal4);
							SumSubpixel(p3+xreal1);
							SumSubpixel(p3+xreal2);
							SumSubpixel(p3+xreal3);
							SumSubpixel(p3+xreal4);
							SumSubpixel(p4+xreal1);
							SumSubpixel(p4+xreal2);
							SumSubpixel(p4+xreal3);
							SumSubpixel(p4+xreal4);
							if(valid==0) {
								*dest++ = 0x73FE;
							}
							else if(valid==255) {
								*dest++ = (0x8000 | r) + (((uint16)g)<<5) + (((uint16)b)<<10);
							}
							else {
								*dest++ = (r/valid) | (((uint16)(g/valid))<<5) | (((uint16)(b/valid))<<10);
							}
						}
					}
					break;
				case 8:
					for(  sint16 y=0;  y<newzoomheight;  y++  ) {
						uint8 *p1 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+0-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p2 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+1-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p3 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+2-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p4 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+3-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p5 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+4-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p6 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+5-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p7 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+6-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						uint8 *p8 = baseimage + baseoff + ((y*zoom_den[zoom_factor]+7-y_rem)/zoom_num[zoom_factor])*(basewidth*4);
						for(  sint16 x=0;  x<newzoomwidth;  x++  ) {
							uint8 valid=0;
							uint16 r=0,g=0,b=0;
							sint16 xreal1 = ((x*zoom_den[zoom_factor]+0-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal2 = ((x*zoom_den[zoom_factor]+1-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal3 = ((x*zoom_den[zoom_factor]+2-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal4 = ((x*zoom_den[zoom_factor]+3-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal5 = ((x*zoom_den[zoom_factor]+4-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal6 = ((x*zoom_den[zoom_factor]+5-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal7 = ((x*zoom_den[zoom_factor]+6-x_rem)/zoom_num[zoom_factor])*4;
							sint16 xreal8 = ((x*zoom_den[zoom_factor]+7-x_rem)/zoom_num[zoom_factor])*4;
							SumSubpixel(p1+xreal1);
							SumSubpixel(p1+xreal2);
							SumSubpixel(p1+xreal3);
							SumSubpixel(p1+xreal4);
							SumSubpixel(p1+xreal5);
							SumSubpixel(p1+xreal6);
							SumSubpixel(p1+xreal7);
							SumSubpixel(p1+xreal8);
							SumSubpixel(p2+xreal1);
							SumSubpixel(p2+xreal2);
							SumSubpixel(p2+xreal3);
							SumSubpixel(p2+xreal4);
							SumSubpixel(p2+xreal5);
							SumSubpixel(p2+xreal6);
							SumSubpixel(p2+xreal7);
							SumSubpixel(p2+xreal8);
							SumSubpixel(p3+xreal1);
							SumSubpixel(p3+xreal2);
							SumSubpixel(p3+xreal3);
							SumSubpixel(p3+xreal4);
							SumSubpixel(p3+xreal5);
							SumSubpixel(p3+xreal6);
							SumSubpixel(p3+xreal7);
							SumSubpixel(p3+xreal8);
							SumSubpixel(p4+xreal1);
							SumSubpixel(p4+xreal2);
							SumSubpixel(p4+xreal3);
							SumSubpixel(p4+xreal4);
							SumSubpixel(p4+xreal5);
							SumSubpixel(p4+xreal6);
							SumSubpixel(p4+xreal7);
							SumSubpixel(p4+xreal8);
							SumSubpixel(p5+xreal1);
							SumSubpixel(p5+xreal2);
							SumSubpixel(p5+xreal3);
							SumSubpixel(p5+xreal4);
							SumSubpixel(p5+xreal5);
							SumSubpixel(p5+xreal6);
							SumSubpixel(p5+xreal7);
							SumSubpixel(p5+xreal8);
							SumSubpixel(p6+xreal1);
							SumSubpixel(p6+xreal2);
							SumSubpixel(p6+xreal3);
							SumSubpixel(p6+xreal4);
							SumSubpixel(p6+xreal5);
							SumSubpixel(p6+xreal6);
							SumSubpixel(p6+xreal7);
							SumSubpixel(p6+xreal8);
							SumSubpixel(p7+xreal1);
							SumSubpixel(p7+xreal2);
							SumSubpixel(p7+xreal3);
							SumSubpixel(p7+xreal4);
							SumSubpixel(p7+xreal5);
							SumSubpixel(p7+xreal6);
							SumSubpixel(p7+xreal7);
							SumSubpixel(p7+xreal8);
							SumSubpixel(p8+xreal1);
							SumSubpixel(p8+xreal2);
							SumSubpixel(p8+xreal3);
							SumSubpixel(p8+xreal4);
							SumSubpixel(p8+xreal5);
							SumSubpixel(p8+xreal6);
							SumSubpixel(p8+xreal7);
							SumSubpixel(p8+xreal8);
							if(valid==0) {
								*dest++ = 0x73FE;
							}
							else if(valid==255) {
								*dest++ = (0x8000 | r) + (((uint16)g)<<5) + (((uint16)b)<<10);
							}
							else {
								*dest++ = (r/valid) | (((uint16)(g/valid))<<5) | (((uint16)(b/valid))<<10);
							}
						}
					}
					break;
				default: assert(0);
			}

			// now encode the image again
			dest = (PIXVAL*)baseimage;
			for(  sint16 y=0;  y<newzoomheight;  y++  ) {
				PIXVAL *line = ((PIXVAL *)baseimage2) + (y*newzoomwidth);
				PIXVAL i;
				sint16 x = 0;
				uint16 clear_colored_run_pair_count = 0;

				do {
					// check length of transparent pixels
					for (i = 0;  line[x] == 0x73FE  &&  x < newzoomwidth;  i++, x++)
						{}
					// first runlength: transparent pixels
					*dest++ = i;
					// copy for non-transparent
					for (i = 0;  line[x] != 0x73FE  &&  x < newzoomwidth;  i++, x++) {
						dest[i + 1] = line[x];
					}

					/* Knightly:
					 *		If it is not the first clear-colored-run pair and its colored run is empty
					 *		--> it is superfluous and can be removed by rolling back the pointer
					 */
					if(  clear_colored_run_pair_count>0  &&  i==0  ) {
						dest--;
						// this only happens at the end of a line, so no need to increment clear_colored_run_pair_count
					}
					else {
						*dest++ = i;	// number of colored pixel
						dest += i;	// skip them
						clear_colored_run_pair_count++;
					}
				} while(x<newzoomwidth);
				*dest++ = 0; // mark line end
			}

			// something left?
			images[n].w = newzoomwidth;
			images[n].h = newzoomheight;
			if(newzoomheight>0) {
				const size_t zoom_len = (size_t)(((uint8 *)dest) - ((uint8 *)baseimage));
				images[n].len = (uint32)(zoom_len/sizeof(PIXVAL));
				images[n].zoom_data = MALLOCN(PIXVAL, images[n].len);
				assert( images[n].zoom_data  );
				memcpy( images[n].zoom_data, baseimage, zoom_len );
			}
		} else {
			if (images[n].w <= 0) {
				// h=0 will be ignored, with w=0 there was an error!
				printf("WARNING: image%d w=0!\n", n);
			}
			images[n].h = 0;
		}
	}
}


/**
 * Holt Helligkeitseinstellungen
 * @author Hj. Malthaner
 */
int display_get_light(void)
{
	return light_level;
}


/* Tomas variant */
static void calc_base_pal_from_night_shift(const int night)
{
	const int night2 = min(night, 4);
	const int day = 4 - night2;
	unsigned int i;

	// constant multiplier 0,66 - dark night  255 will drop to 49, 55 to 10
	//                     0,7  - dark, but all is visible     61        13
	//                     0,73                                72        15
	//                     0,75 - quite bright                 80        17
	//                     0,8    bright                      104        22

	const double RG_nihgt_multiplier = pow(0.75, night) * ((light_level + 8.0) / 8.0);
	const double B_nihgt_multiplier  = pow(0.83, night) * ((light_level + 8.0) / 8.0);

	for (i = 0; i < 0x8000; i++) {
		// (1<<15) this is total no of all possible colors in RGB555)
		// RGB 555 input
		int R = (i & 0x7C00) >> 7;
		int G = (i & 0x03E0) >> 2;
		int B = (i & 0x001F) << 3;
		// lines generate all possible colors in 555RGB code - input
		// however the result is in 888RGB - 8bit per channel

		R = (int)(R * RG_nihgt_multiplier);
		G = (int)(G * RG_nihgt_multiplier);
		B = (int)(B * B_nihgt_multiplier);

		rgbmap_day_night[i] = get_system_color(R, G, B);
	}

	// player color map (and used for map display etc.)
	for (i = 0; i < 224; i++) {
		const int R = (int)(special_pal[i*3 + 0] * RG_nihgt_multiplier);
		const int G = (int)(special_pal[i*3 + 1] * RG_nihgt_multiplier);
		const int B = (int)(special_pal[i*3 + 2] * B_nihgt_multiplier);

		specialcolormap_day_night[i] = get_system_color(R, G, B);
	}
	// special light colors (actually, only non-darkening greys should be used
	for(i=0;  i<LIGHT_COUNT;  i++  ) {
		specialcolormap_day_night[i+224] = get_system_color( day_lights[i*3 + 0], day_lights[i*3 + 1], 	day_lights[i*3 + 2] );
	}
	// init with black for forbidden colors
	for(i=224+LIGHT_COUNT;  i<256;  i++  ) {
		specialcolormap_day_night[i] = 0;
	}
	// default player colors
	for(i=0;  i<8;  i++  ) {
		rgbmap_day_night[0x8000+i] = specialcolormap_day_night[player_offsets[0][0]+i];
		rgbmap_day_night[0x8008+i] = specialcolormap_day_night[player_offsets[0][1]+i];
	}
	player_night = 0;

	// Lights
	for (i = 0; i < LIGHT_COUNT; i++) {
		const int day_R = day_lights[i*3+0];
		const int day_G = day_lights[i*3+1];
		const int day_B = day_lights[i*3+2];

		const int night_R = night_lights[i*3+0];
		const int night_G = night_lights[i*3+1];
		const int night_B = night_lights[i*3+2];

		const int R = (day_R * day + night_R * night2) >> 2;
		const int G = (day_G * day + night_G * night2) >> 2;
		const int B = (day_B * day + night_B * night2) >> 2;

		rgbmap_day_night[0x8010 + i] =
			get_system_color(R > 0 ? R : 0, G > 0 ? G : 0, B > 0 ? B : 0);
	}

	// convert to RGB xxx
	recode();
}


/**
 * Setzt Helligkeitseinstellungen
 * @author Hj. Malthaner
 */
void display_set_light(int new_light_level)
{
	light_level = new_light_level;
	calc_base_pal_from_night_shift(night_shift);
	recode();
}


void display_day_night_shift(int night)
{
	if (night != night_shift) {
		night_shift = night;
		calc_base_pal_from_night_shift(night);
		mark_rect_dirty_nc(0, 32, disp_width - 1, disp_height - 1);
	}
}



// set first and second company color for player
void display_set_player_color_scheme(const int player, const COLOR_VAL col1, const COLOR_VAL col2 )
{
	if(player_offsets[player][0]!=col1  ||  player_offsets[player][1]!=col2) {
		// set new player colors
		player_offsets[player][0] = col1;
		player_offsets[player][1] = col2;
		if(player==player_day  ||  player==player_night) {
			// and recalculate map (and save it)
			calc_base_pal_from_night_shift(0);
			memcpy(rgbmap_all_day, rgbmap_day_night, RGBMAPSIZE * sizeof(PIXVAL));
			if(night_shift!=0) {
				calc_base_pal_from_night_shift(night_shift);
			}
			player_day = player_night = player;
		}
		recode();
		mark_rect_dirty_nc(0, 32, disp_width - 1, disp_height - 1);
	}
}


void register_image(struct bild_t* bild)
{
	struct imd* image;

	/* valid image? */
	if(  bild->len == 0  ||  bild->h == 0  ) {
		fprintf(stderr, "Warning: ignoring image %d because of missing data\n", anz_images);
		bild->bild_nr = IMG_LEER;
		return;
	}

	if(  anz_images >= 65535  ) {
		fprintf(stderr, "FATAL:\n*** Out of images (more than 65534!) ***\n");
		abort();
	}

	if(  anz_images == alloc_images  ) {
		alloc_images += 128;
		images = REALLOC(images, imd, alloc_images);
	}

	bild->bild_nr = anz_images;
	image = &images[anz_images];
	anz_images++;

	image->x = bild->x;
	image->w = bild->w;
	image->y = bild->y;
	image->h = bild->h;

	image->base_x = bild->x;
	image->base_w = bild->w;
	image->base_y = bild->y;
	image->base_h = bild->h;

	image->len = bild->len;

	// allocate and copy if needed
	image->recode_flags = FLAG_NORMAL_RECODE | FLAG_REZOOM | (bild->zoomable & FLAG_ZOOMABLE);
	image->player_flags = NEED_PLAYER_RECODE;

	image->base_data = NULL;
	image->zoom_data = NULL;
	image->data = NULL;
	image->player_data = NULL;	// chaches data for one AI

	// since we do not recode them, we can work with the original data
	image->base_data = bild->data;

	// does this image have color?
	if(  bild->h > 0  ) {
		int h = bild->h;
		const PIXVAL *src = image->base_data;

		do {
			src++; // offset of first start
			do {
				uint16 runlen;

				for (runlen = *src++; runlen != 0; runlen--) {
					PIXVAL pix = *src++;

					if (pix>=0x8000  &&  pix<=0x800F) {
						image->recode_flags |= FLAG_PLAYERCOLOR;
						return;
					}
				}
				// next clear run or zero = end
			} while(  *src++ != 0  );
		} while(  --h != 0  );
	}
}


// delete all images above a certain number ...
// (mostly needed when changing climate zones)
void display_free_all_images_above( unsigned above )
{
	while( above<anz_images) {
		anz_images--;
		if(images[anz_images].zoom_data!=NULL) {
			guarded_free( images[anz_images].zoom_data );
		}
		if(images[anz_images].player_data!=NULL) {
			guarded_free( images[anz_images].player_data );
		}
		if(images[anz_images].data!=NULL) {
			guarded_free( images[anz_images].data );
		}
	}
}


// prissi: query offsets
void display_get_image_offset(unsigned bild, KOORD_VAL *xoff, KOORD_VAL *yoff, KOORD_VAL *xw, KOORD_VAL *yw)
{
	if (bild < anz_images) {
		*xoff = images[bild].x;
		*yoff = images[bild].y;
		*xw   = images[bild].w;
		*yw   = images[bild].h;
	}
}


// prissi: query unzoiomed offsets
void display_get_base_image_offset(unsigned bild, KOORD_VAL *xoff, KOORD_VAL *yoff, KOORD_VAL *xw, KOORD_VAL *yw)
{
	if (bild < anz_images) {
		*xoff = images[bild].base_x;
		*yoff = images[bild].base_y;
		*xw   = images[bild].base_w;
		*yw   = images[bild].base_h;
	}
}


// prissi: changes the offset of an image
// we need it this complex, because the actual x-offset is coded into the image
void display_set_base_image_offset(unsigned bild, KOORD_VAL xoff, KOORD_VAL yoff)
{
	if(bild >= anz_images) {
		fprintf(stderr, "Warning: display_set_base_image_offset(): illegal image=%d\n", bild);
		return;
	}

	// only move images once
	if(  images[bild].recode_flags & FLAG_POSITION_CHANGED  ) {
		fprintf(stderr, "Warning: display_set_base_image_offset(): image=%d was already moved!\n", bild);
		return;
	}
	images[bild].recode_flags |= FLAG_POSITION_CHANGED;

	assert(images[bild].base_h > 0);
	assert(images[bild].base_w > 0);

	// avoid overflow
	images[bild].base_x += xoff;
	images[bild].base_y += yoff;
}


// ------------------ display all kind of images from here on ------------------------------


/**
 * Kopiert Pixel von src nach dest
 * @author Hj. Malthaner
 */
static inline void pixcopy(PIXVAL *dest, const PIXVAL *src, const PIXVAL * const end)
{
	// for gcc this seems to produce the optimal code ...
	while (src < end) {
		*dest++ = *src++;
	}
}


/**
 * Kopiert Pixel, ersetzt Spielerfarben
 * @author Hj. Malthaner
 */
static inline void colorpixcopy(PIXVAL *dest, const PIXVAL *src, const PIXVAL * const end)
{
	while (src < end) {
		*dest++ = rgbmap_current[*src++];
	}
}

/**
 * templated pixel copy routines
 * to be used in display_img_pc
 */
enum pixcopy_routines {
	plain = 0,	/// simply copies the pixels
	colored = 1	/// replaces player colors
};

template<pixcopy_routines copyroutine> void templated_pixcopy(PIXVAL *dest, const PIXVAL *src, const PIXVAL * const end);
template<> void templated_pixcopy<plain>(PIXVAL *dest, const PIXVAL *src, const PIXVAL * const end)
{
	pixcopy(dest, src, end);
}
template<> void templated_pixcopy<colored>(PIXVAL *dest, const PIXVAL *src, const PIXVAL * const end)
{
	colorpixcopy(dest, src, end);
}

/**
 * draws image with clipping along arbitrary lines
 * @author Dwachs
 */
template<pixcopy_routines copyroutine>
static void display_img_pc(KOORD_VAL h, const KOORD_VAL xp, const KOORD_VAL yp, const PIXVAL *sp)
{
	if (h > 0) {
		PIXVAL *tp = textur + yp * disp_width;

		// initialize clipping
		init_ranges(yp);

		do { // zeilen dekodieren
			int xpos = xp;

			// bild darstellen
			int runlen = *sp++;

			// get left/right boundary, step
			int xmin, xmax;
			get_xrange_and_step_y(xmin, xmax);

			do {
				// wir starten mit einem clear run
				xpos += runlen;

				// jetzt kommen farbige pixel
				runlen = *sp++;

				// Hajo: something to display?
				if (xmin < xmax  &&  xpos + runlen > xmin && xpos < xmax) {
					const int left = (xpos >= xmin ? 0 : xmin - xpos);
					const int len  = (xmax - xpos >= runlen ? runlen : xmax - xpos);

					templated_pixcopy<copyroutine>(tp + xpos + left, sp + left, sp + len);
				}

				sp += runlen;
				xpos += runlen;
			} while ((runlen = *sp++));

			tp += disp_width;
		} while (--h);
	}
}


/**
 * Zeichnet Bild mit horizontalem Clipping
 * @author Hj. Malthaner
 */
static void display_img_wc(KOORD_VAL h, const KOORD_VAL xp, const KOORD_VAL yp, const PIXVAL *sp)
{
	if (h > 0) {
		PIXVAL *tp = textur + yp * disp_width;

		do { // zeilen dekodieren
			int xpos = xp;

			// bild darstellen
			uint16 runlen = *sp++;

			do {
				// wir starten mit einem clear run
				xpos += runlen;

				// jetzt kommen farbige pixel
				runlen = *sp++;

				// Hajo: something to display?
				if (xpos + runlen > clip_rect.x && xpos < clip_rect.xx) {
					const int left = (xpos >= clip_rect.x ? 0 : clip_rect.x - xpos);
					const int len  = (clip_rect.xx - xpos >= runlen ? runlen : clip_rect.xx - xpos);

					pixcopy(tp + xpos + left, sp + left, sp + len);
				}

				sp += runlen;
				xpos += runlen;
			} while ((runlen = *sp++));

			tp += disp_width;
		} while (--h);
	}
}


/**
 * Zeichnet Bild ohne jedes Clipping
 */
static void display_img_nc(KOORD_VAL h, const KOORD_VAL xp, const KOORD_VAL yp, const PIXVAL *sp)
{
	if (h > 0) {
		PIXVAL *tp = textur + xp + yp * disp_width;

		do { // zeilen dekodieren
#ifdef USE_C
			uint16 runlen = *sp++;
#else
			// assembler needs this size
			uint32 runlen = *sp++;
#endif
			PIXVAL *p = tp;

			// eine Zeile dekodieren
			do {
				// wir starten mit einem clear run
				p += runlen;

				// jetzt kommen farbige pixel
				runlen = *sp++;
#ifdef USE_C
#ifndef ALIGN_COPY
				{
					// "classic" C code (why is it faster!?!)
					const uint32 *ls;
					uint32 *ld;

					if (runlen & 1) {
						*p++ = *sp++;
					}

					ls = (const uint32 *)sp;
					ld = (uint32 *)p;
					runlen >>= 1;
					while (runlen--) {
						*ld++ = *ls++;
					}
					p = (PIXVAL*)ld;
					sp = (const PIXVAL*)ls;
				}
#else
				// some architectures: faster with inline of memory functions!
				memcpy( p, sp, runlen*sizeof(PIXVAL) );
				sp += runlen;
				p += runlen;
#endif
#else
				// this code is sometimes slower, mostly 5% faster, not really clear why and when (cache alignment?)
				asm volatile (
					// rep movsw and we would be finished, but we unroll
					// uneven number of words to copy
					"shrl %2\n\t"
					"jnc 0f\n\t"
					// Copy first word
					// *p++ = *sp++;
					"movsw\n\t"
					"0:\n\t"
					"negl %2\n\t"
					"addl $1f, %2\n\t"
					"jmp * %2\n\t"
					".p2align 2\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"


					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"

					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n\t"
					"movsd\n"

					"1:\n\t"

					: "+D" (p), "+S" (sp), "+r" (runlen)
					:
					: "cc", "memory"
				);
#endif
				runlen = *sp++;
			} while (runlen != 0);

			tp += disp_width;
		} while (--h > 0);
	}
}


/**
 * Zeichnet Bild mit verticalem clipping (schnell) und horizontalem (langsam)
 * @author prissi
 */
void display_img_aux(const unsigned n, KOORD_VAL xp, KOORD_VAL yp, const sint8 use_player, const int /*daynight*/, const int dirty)
{
	if (n < anz_images) {
		// need to go to nightmode and or rezoomed?
		PIXVAL *sp;
		KOORD_VAL h, reduce_h, skip_lines;

		if (use_player) {
			sp = images[n].player_data;
			if (sp == NULL) {
				printf("CImg %i failed!\n", n);
				return;
			}
		} else {
			if (images[n].recode_flags&FLAG_REZOOM) {
				rezoom_img(n);
				recode_normal_img(n);
			} else if (images[n].recode_flags&FLAG_NORMAL_RECODE) {
				recode_normal_img(n);
			}
			sp = images[n].data;
			if (sp == NULL) {
				printf("Img %i failed!\n", n);
				return;
			}
		}
		// now, since zooming may have change this image
		yp += images[n].y;
		h = images[n].h; // may change due to vertical clipping

		// in the next line the vertical clipping will be handled
		// by that way the drawing routines must only take into account the horizontal clipping
		// this should be much faster in most cases

		// must the height be reduced?
		reduce_h = yp + h - clip_rect.yy;
		if (reduce_h > 0) {
			h -= reduce_h;
		}
		// still something to draw
		if (h <= 0) {
			return;
		}

		// vertically lines to skip (only bottom is visible
		skip_lines = clip_rect.y - (int)yp;
		if (skip_lines > 0) {
			if (skip_lines >= h) {
				// not visible at all
				return;
			}
			h -= skip_lines;
			yp += skip_lines;
			// now skip them
			while (skip_lines--) {
				do {
					// clear run + colored run + next clear run
					sp++;
					sp += *sp + 1;
				} while (*sp);
				sp++;
			}
			// now sp is the new start of an image with height h
		}

		// new block for new variables
		{
			// needed now ...
			const KOORD_VAL w = images[n].w;
			xp += images[n].x;

			// clipping at poly lines?
			if (number_of_clips>0) {
					display_img_pc<plain>(h, xp, yp, sp);
					// since height may be reduced, start marking here
					if (dirty) {
						mark_rect_dirty_wc(xp, yp, xp + w - 1, yp + h - 1);
					}
			}
			else {
				// use horizontal clipping or skip it?
				if (xp >= clip_rect.x  &&  xp + w <= clip_rect.xx) {
					// marking change?
					if (dirty) {
						mark_rect_dirty_nc(xp, yp, xp + w - 1, yp + h - 1);
					}
					display_img_nc(h, xp, yp, sp);
				} else if (xp < clip_rect.xx  &&  xp + w > clip_rect.x) {
					display_img_wc(h, xp, yp, sp);
					// since height may be reduced, start marking here
					if (dirty) {
						mark_rect_dirty_wc(xp, yp, xp + w - 1, yp + h - 1);
					}
				}
			}
		}
	}
}


/**
 * Zeichnet Bild, ersetzt Spielerfarben
 * assumes height is ok and valid data are caluclated
 * color replacement needs the original data => sp points to non-cached data
 * @author hajo/prissi
 */
static void display_color_img_wc(const PIXVAL *sp, KOORD_VAL x, KOORD_VAL y, KOORD_VAL h )
{
	PIXVAL *tp = textur + y * disp_width;

	do { // zeilen dekodieren
		int xpos = x;

		// bild darstellen

		uint16 runlen = *sp++;

		do {
			// wir starten mit einem clear run
			xpos += runlen;

			// jetzt kommen farbige pixel
			runlen = *sp++;

			// Hajo: something to display?
			if (xpos + runlen > clip_rect.x && xpos < clip_rect.xx) {
				const int left = (xpos >= clip_rect.x ? 0 : clip_rect.x - xpos);
				const int len  = (clip_rect.xx-xpos > runlen ? runlen : clip_rect.xx - xpos);

				colorpixcopy(tp + xpos + left, sp + left, sp + len);
			}

			sp += runlen;
			xpos += runlen;
		} while ((runlen = *sp++));

		tp += disp_width;
	} while (--h);
}


/**
 * Zeichnet Bild, ersetzt Spielerfarben
 * @author Hj. Malthaner
 */
void display_color_img(const unsigned n, KOORD_VAL xp, KOORD_VAL yp, const sint8 player_nr, const int daynight, const int dirty)
{
	if (n < anz_images) {

		// first: size check
		if (images[n].recode_flags&FLAG_REZOOM) {
			rezoom_img(n);
		}

		if(  daynight  ||  night_shift==0  ) {
			// Hajo: since the colors for player 0 are already right,
			// only use the expensive replacement routine for colored images
			// of other players
			if(  player_nr==0  ||  (images[n].recode_flags & FLAG_PLAYERCOLOR)==0  ) {
				display_img_aux(n, xp, yp, false, true, dirty);
				return;
			}

			// first test, if we can/need to build a cached version
			if(  (images[n].player_flags&(~NEED_PLAYER_RECODE)) == 0  ) {
				// we can still recoler if needed
				recode_color_img(n, player_nr);
			}
			// ok, there is a cached version
			if(  (images[n].player_flags&(~NEED_PLAYER_RECODE)) == player_nr  ) {
				// ok, now we could use the same faster code as for the normal images
				display_img_aux(n, xp, yp, true, true, dirty);
				return;
			}
		}

		// prissi: now test if visible and clipping needed
		{
			const KOORD_VAL x = images[n].x + xp;
			      KOORD_VAL y = images[n].y + yp;
			const KOORD_VAL w = images[n].w;
			      KOORD_VAL h = images[n].h;

			if (h <= 0 || x >= clip_rect.xx || y >= clip_rect.yy || x + w <= clip_rect.x || y + h <= clip_rect.y) {
				// not visible => we are done
				// happens quite often ...
				return;
			}

			if (dirty) {
				mark_rect_dirty_wc(x, y, x + w - 1, y + h - 1);
			}

			// colors for 2nd company color
			if(player_nr>=0) {
				activate_player_color( player_nr, daynight );
			}
			else {
				// no player
				activate_player_color( 0, daynight );
			}
			// color replacement needs the original data => sp points to non-cached data
			const PIXVAL *sp = (tile_raster_width != base_tile_raster_width  &&  images[n].zoom_data != NULL) ? images[n].zoom_data : images[n].base_data;

			// clip top/bottom
			KOORD_VAL yoff = clip_wh(&y, &h, clip_rect.y, clip_rect.yy);
			if (h > 0) { // clipping may have reduced it

				// oben clippen
				while (yoff) {
					yoff--;
					do {
						// clear run + colored run + next clear run
						++sp;
						sp += *sp + 1;
					} while (*sp);
					sp++;
				}

				// clipping at poly lines?
				if (number_of_clips>0) {
					display_img_pc<colored>(h, x, y, sp);
				}
				else {
					display_color_img_wc(sp, x, y, h );
				}
			}
		}
	} // number ok
}



/**
 * draw unscaled images, replaces base color
 * @author prissi
 */
void display_base_img(const unsigned n, KOORD_VAL xp, KOORD_VAL yp, const sint8 player_nr, const int daynight, const int dirty)
{
	if(  base_tile_raster_width==tile_raster_width  ) {
		// same size => use standard routine
		display_color_img( n, xp, yp, player_nr, daynight, dirty );
	}
	else if (n < anz_images) {

		// prissi: now test if visible and clipping needed
		const KOORD_VAL x = images[n].base_x + xp;
		      KOORD_VAL y = images[n].base_y + yp;
		const KOORD_VAL w = images[n].base_w;
		      KOORD_VAL h = images[n].base_h;

		if (h <= 0 || x >= clip_rect.xx || y >= clip_rect.yy || x + w <= clip_rect.x || y + h <= clip_rect.y) {
			// not visible => we are done
			// happens quite often ...
			return;
		}

		if (dirty) {
			mark_rect_dirty_wc(x, y, x + w - 1, y + h - 1);
		}

		// colors for 2nd company color
		if(player_nr>=0) {
			activate_player_color( player_nr, daynight );
		}
		else {
			// no player
			activate_player_color( 0, daynight );
		}

		// color replacement needs the original data => sp points to non-cached data
		const PIXVAL *sp = images[n].base_data;

		// clip top/bottom
		KOORD_VAL yoff = clip_wh(&y, &h, clip_rect.y, clip_rect.yy);
		if (h > 0) { // clipping may have reduced it

			// oben clippen
			while (yoff) {
				yoff--;
				do {
					// clear run + colored run + next clear run
					++sp;
					sp += *sp + 1;
				} while (*sp);
				sp++;
			}
			display_color_img_wc( sp, x, y, h );
		}

	} // number ok
}



/* from here code for transparent images */
typedef void (*blend_proc)(PIXVAL *dest, const PIXVAL *src, const PIXVAL colour, const PIXVAL len);

// different masks needed for RGB 555 and RGB 565
#define ONE_OUT_16 (0x7bef)
#define TWO_OUT_16 (0x39E7)
#define ONE_OUT_15 (0x3DEF)
#define TWO_OUT_15 (0x1CE7)

static void pix_blend75_15(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (3*(((*src)>>2) & TWO_OUT_15)) + (((*dest)>>2) & TWO_OUT_15);
		dest++;
		src++;
	}
}

static void pix_blend75_16(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (3*(((*src)>>2) & TWO_OUT_16)) + (((*dest)>>2) & TWO_OUT_16);
		dest++;
		src++;
	}
}

static void pix_blend50_15(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((*src)>>1) & ONE_OUT_15) + (((*dest)>>1) & ONE_OUT_15);
		dest++;
		src++;
	}
}

static void pix_blend50_16(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((*src)>>1) & ONE_OUT_16) + (((*dest)>>1) & ONE_OUT_16);
		dest++;
		src++;
	}
}

static void pix_blend25_15(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((*src)>>2) & TWO_OUT_15) + (3*(((*dest)>>2) & TWO_OUT_15));
		dest++;
		src++;
	}
}

static void pix_blend25_16(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((*src)>>2) & TWO_OUT_16) + (3*(((*dest)>>2) & TWO_OUT_16));
		dest++;
		src++;
	}
}

// Knightly : the following 6 functions are for display_base_img_blend()
static void pix_blend_recode75_15(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (3*(((rgbmap_current[*src])>>2) & TWO_OUT_15)) + (((*dest)>>2) & TWO_OUT_15);
		dest++;
		src++;
	}
}

static void pix_blend_recode75_16(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (3*(((rgbmap_current[*src])>>2) & TWO_OUT_16)) + (((*dest)>>2) & TWO_OUT_16);
		dest++;
		src++;
	}
}

static void pix_blend_recode50_15(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((rgbmap_current[*src])>>1) & ONE_OUT_15) + (((*dest)>>1) & ONE_OUT_15);
		dest++;
		src++;
	}
}

static void pix_blend_recode50_16(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((rgbmap_current[*src])>>1) & ONE_OUT_16) + (((*dest)>>1) & ONE_OUT_16);
		dest++;
		src++;
	}
}

static void pix_blend_recode25_15(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((rgbmap_current[*src])>>2) & TWO_OUT_15) + (3*(((*dest)>>2) & TWO_OUT_15));
		dest++;
		src++;
	}
}

static void pix_blend_recode25_16(PIXVAL *dest, const PIXVAL *src, const PIXVAL , const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (((rgbmap_current[*src])>>2) & TWO_OUT_16) + (3*(((*dest)>>2) & TWO_OUT_16));
		dest++;
		src++;
	}
}

static void pix_outline75_15(PIXVAL *dest, const PIXVAL *, const PIXVAL colour, const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (3*((colour>>2) & TWO_OUT_15)) + (((*dest)>>2) & TWO_OUT_15);
		dest++;
	}
}

static void pix_outline75_16(PIXVAL *dest, const PIXVAL *, const PIXVAL colour, const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = (3*((colour>>2) & TWO_OUT_16)) + (((*dest)>>2) & TWO_OUT_16);
		dest++;
	}
}

static void pix_outline50_15(PIXVAL *dest, const PIXVAL *, const PIXVAL colour, const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = ((colour>>1) & ONE_OUT_15) + (((*dest)>>1) & ONE_OUT_15);
		dest++;
	}
}

static void pix_outline50_16(PIXVAL *dest, const PIXVAL *, const PIXVAL colour, const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = ((colour>>1) & ONE_OUT_16) + (((*dest)>>1) & ONE_OUT_16);
		dest++;
	}
}

static void pix_outline25_15(PIXVAL *dest, const PIXVAL *, const PIXVAL colour, const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = ((colour>>2) & TWO_OUT_15) + (3*(((*dest)>>2) & TWO_OUT_15));
		dest++;
	}
}

static void pix_outline25_16(PIXVAL *dest, const PIXVAL *, const PIXVAL colour, const PIXVAL len)
{
	const PIXVAL *const end = dest + len;
	while (dest < end) {
		*dest = ((colour>>2) & TWO_OUT_16) + (3*(((*dest)>>2) & TWO_OUT_16));
		dest++;
	}
}

// will kept the actual values
static blend_proc blend[3];
static blend_proc blend_recode[3];
static blend_proc outline[3];

static void display_img_blend_wc(KOORD_VAL h, const KOORD_VAL xp, const KOORD_VAL yp, const PIXVAL *sp, int colour, blend_proc p )
{
	if (h > 0) {
		PIXVAL *tp = textur + yp * disp_width;

		do { // zeilen dekodieren
			int xpos = xp;

			// bild darstellen
			uint16 runlen = *sp++;

			do {
				// wir starten mit einem clear run
				xpos += runlen;

				// jetzt kommen farbige pixel
				runlen = *sp++;

				// Hajo: something to display?
				if (xpos + runlen > clip_rect.x && xpos < clip_rect.xx) {
					const int left = (xpos >= clip_rect.x ? 0 : clip_rect.x - xpos);
					const int len  = (clip_rect.xx - xpos >= runlen ? runlen : clip_rect.xx - xpos);
					p(tp + xpos + left, sp + left, colour, len - left);
				}

				sp += runlen;
				xpos += runlen;
			} while ((runlen = *sp++));

			tp += disp_width;
		} while (--h);
	}
}

/**
 * draws the transparent outline of an image
 * @author kierongreen
 */
void display_rezoomed_img_blend(const unsigned n, KOORD_VAL xp, KOORD_VAL yp, const signed char /*player_nr*/, const PLAYER_COLOR_VAL color_index, const int /*daynight*/, const int dirty)
{
	if (n < anz_images) {
		// need to go to nightmode and or rezoomed?
		PIXVAL *sp;
		KOORD_VAL h, reduce_h, skip_lines;

		if (images[n].recode_flags&FLAG_REZOOM) {
			rezoom_img(n);
			recode_normal_img(n);
		} else if (images[n].recode_flags&FLAG_NORMAL_RECODE) {
			recode_normal_img(n);
		}
		sp = images[n].data;

		// now, since zooming may have change this image
		xp += images[n].x;
		yp += images[n].y;
		h = images[n].h; // may change due to vertical clipping

		// in the next line the vertical clipping will be handled
		// by that way the drawing routines must only take into account the horizontal clipping
		// this should be much faster in most cases

		// must the height be reduced?
		reduce_h = yp + h - clip_rect.yy;
		if (reduce_h > 0) {
			h -= reduce_h;
		}
		// still something to draw
		if (h <= 0) return;

		// vertically lines to skip (only bottom is visible
		skip_lines = clip_rect.y - (int)yp;
		if (skip_lines > 0) {
			if (skip_lines >= h) {
				// not visible at all
				return;
			}
			h -= skip_lines;
			yp += skip_lines;
			// now skip them
			while (skip_lines--) {
				do {
					// clear run + colored run + next clear run
					sp++;
					sp += *sp + 1;
				} while (*sp);
				sp++;
			}
			// now sp is the new start of an image with height h
		}

		// new block for new variables
		{
			// needed now ...
			const KOORD_VAL w = images[n].w;
			// get the real color
			const PIXVAL color = specialcolormap_all_day[color_index & 0xFF];
			// we use function pointer for the blend runs for the moment ...
			blend_proc pix_blend = (color_index&OUTLINE_FLAG) ? outline[ (color_index&TRANSPARENT_FLAGS)/TRANSPARENT25_FLAG - 1 ] : blend[ (color_index&TRANSPARENT_FLAGS)/TRANSPARENT25_FLAG - 1 ];

			// use horzontal clipping or skip it?
			if (xp >= clip_rect.x && xp + w  <= clip_rect.xx) {
				// marking change?
				if (dirty) {
					mark_rect_dirty_nc(xp, yp, xp + w - 1, yp + h - 1);
				}
				display_img_blend_wc( h, xp, yp, sp, color, pix_blend );
			} else if (xp < clip_rect.xx && xp + w > clip_rect.x) {
				display_img_blend_wc( h, xp, yp, sp, color, pix_blend );
				// since height may be reduced, start marking here
				if (dirty) {
					mark_rect_dirty_wc(xp, yp, xp + w - 1, yp + h - 1);
				}
			}
		}
	}
}



// Knightly : For blending or outlining unzoomed image. Adapted from display_base_img() and display_unzoomed_img_blend()
void display_base_img_blend(const unsigned n, KOORD_VAL xp, KOORD_VAL yp, const signed char player_nr, const PLAYER_COLOR_VAL color_index, const int daynight, const int dirty)
{
	if(  base_tile_raster_width==tile_raster_width  ) {
		// same size => use standard routine
		display_rezoomed_img_blend( n, xp, yp, player_nr, color_index, daynight, dirty );
	}
	else if (n < anz_images) {

		// prissi: now test if visible and clipping needed
		KOORD_VAL x = images[n].base_x + xp;
		KOORD_VAL y = images[n].base_y + yp;
		KOORD_VAL w = images[n].base_w;
		KOORD_VAL h = images[n].base_h;

		if (h == 0 || x >= clip_rect.xx || y >= clip_rect.yy || x + w <= clip_rect.x || y + h <= clip_rect.y) {
			// not visible => we are done
			// happens quite often ...
			return;
		}

		PIXVAL *sp = images[n].base_data;

		// must the height be reduced?
		KOORD_VAL reduce_h = y + h - clip_rect.yy;
		if (reduce_h > 0) {
			h -= reduce_h;
		}

		// vertical lines to skip (only bottom is visible)
		KOORD_VAL skip_lines = clip_rect.y - (int)y;
		if (skip_lines > 0) {
			h -= skip_lines;
			y += skip_lines;
			// now skip them
			while (skip_lines--) {
				do {
					// clear run + colored run + next clear run
					sp++;
					sp += *sp + 1;
				} while (*sp);
				sp++;
			}
			// now sp is the new start of an image with height h
		}

		// new block for new variables
		{
			const PIXVAL color = specialcolormap_all_day[color_index & 0xFF];
			blend_proc pix_blend = (color_index&OUTLINE_FLAG) ? outline[ (color_index&TRANSPARENT_FLAGS)/TRANSPARENT25_FLAG - 1 ] : blend_recode[ (color_index&TRANSPARENT_FLAGS)/TRANSPARENT25_FLAG - 1 ];

			// recode is needed only for blending
			if(  !(color_index&OUTLINE_FLAG)  ) {
				// colors for 2nd company color
				if(player_nr>=0) {
					activate_player_color( player_nr, daynight );
				}
				else {
					// no player
					activate_player_color( 0, daynight );
				}
			}

			// use horizontal clipping or skip it?
			if(  x>=clip_rect.x  &&  x+w<=clip_rect.xx  ) {
				if (dirty) {
					mark_rect_dirty_nc(x, y, x + w - 1, y + h - 1);
				}
				display_img_blend_wc( h, x, y, sp, color, pix_blend );
			}
			else {
				if (dirty) {
					mark_rect_dirty_wc(x, y, x + w - 1, y + h - 1);
				}
				display_img_blend_wc( h, x, y, sp, color, pix_blend );
			}
		}
	} // number ok
}



// ----------------- basic painting procedures ----------------


// scrolls horizontally, will ignore clipping etc.
void display_scroll_band(const KOORD_VAL start_y, const KOORD_VAL x_offset, const KOORD_VAL h)
{
	const PIXVAL* src = textur + start_y * disp_width + x_offset;
	PIXVAL* dst = textur + start_y * disp_width;
	size_t amount = sizeof(PIXVAL) * (h * disp_width - x_offset);

#ifdef USE_C
	memmove(dst, src, amount);
#else
	amount /= 4;
	asm volatile (
		"rep\n\t"
		"movsl\n\t"
		: "+D" (dst), "+S" (src), "+c" (amount)
	);
#endif
}


/**
 * Zeichnet ein Pixel
 * @author Hj. Malthaner
 */
static void display_pixel(KOORD_VAL x, KOORD_VAL y, PIXVAL color)
{
	if (x >= clip_rect.x && x < clip_rect.xx && y >= clip_rect.y && y < clip_rect.yy) {
		PIXVAL* const p = textur + x + y * disp_width;

		*p = color;
		mark_tile_dirty(x >> DIRTY_TILE_SHIFT, y >> DIRTY_TILE_SHIFT);
	}
}


/**
 * Zeichnet gefuelltes Rechteck
 */
static void display_fb_internal(KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL w, KOORD_VAL h, int color, int dirty, KOORD_VAL cL, KOORD_VAL cR, KOORD_VAL cT, KOORD_VAL cB)
{
	if (clip_lr(&xp, &w, cL, cR) && clip_lr(&yp, &h, cT, cB)) {
		PIXVAL *p = textur + xp + yp * disp_width;
		int dx = disp_width - w;
		const PIXVAL colval = specialcolormap_all_day[color & 0xFF];
#if !defined( USE_C )  ||  !defined( ALIGN_COPY )
		const uint32 longcolval = (colval << 16) | colval;
#endif

		if (dirty) {
			mark_rect_dirty_nc(xp, yp, xp + w - 1, yp + h - 1);
		}

		do {
#ifdef USE_C
			KOORD_VAL count = w;
#ifdef ALIGN_COPY
			// unfourtunately the GCC > 4.1.x has a bug in the optimizer
			while(  count-- != 0  ) {
				*p++ = colval;
			}
#else
			uint32 *lp;

			if(  count & 1  ) {
				*p++ = colval;
			}
			count >>= 1;
			lp = (uint32 *)p;
			while(  count-- != 0  ) {
				*lp++ = longcolval;
			}
			p = (PIXVAL *)lp;
#endif
#else
			unsigned int count = w;
			asm volatile (
				// uneven words to copy?
				"shrl %1\n\t"
				"jnc 0f\n\t"
				// set first word
				"stosw\n\t"
				"0:\n\t"
				// now we set long words ...
				"rep\n\t"
				"stosl"
				: "+D" (p), "+c" (count)
				: "a" (longcolval)
				: "cc", "memory"
			);
#endif
			p += dx;
		} while (--h != 0);
	}
}


void display_fillbox_wh(KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL w, KOORD_VAL h, PLAYER_COLOR_VAL color, int dirty)
{
	display_fb_internal(xp, yp, w, h, color, dirty, 0, disp_width, 0, disp_height);
}


void display_fillbox_wh_clip(KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL w, KOORD_VAL h, PLAYER_COLOR_VAL color, int dirty)
{
	display_fb_internal(xp, yp, w, h, color, dirty, clip_rect.x, clip_rect.xx, clip_rect.y, clip_rect.yy);
}


/**
 * Zeichnet vertikale Linie
 * @author Hj. Malthaner
 */
static void display_vl_internal(const KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL h, const PLAYER_COLOR_VAL color, int dirty, KOORD_VAL cL, KOORD_VAL cR, KOORD_VAL cT, KOORD_VAL cB)
{
	if (xp >= cL && xp < cR && clip_lr(&yp, &h, cT, cB)) {
		PIXVAL *p = textur + xp + yp * disp_width;
		const PIXVAL colval =specialcolormap_all_day[color & 0xFF];

		if (dirty) mark_rect_dirty_nc(xp, yp, xp, yp + h - 1);

		do {
			*p = colval;
			p += disp_width;
		} while (--h != 0);
	}
}


void display_vline_wh(const KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL h, const PLAYER_COLOR_VAL color, int dirty)
{
	display_vl_internal(xp, yp, h, color, dirty, 0, disp_width, 0, disp_height);
}


void display_vline_wh_clip(const KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL h, const PLAYER_COLOR_VAL color, int dirty)
{
	display_vl_internal(xp, yp, h, color, dirty, clip_rect.x, clip_rect.xx, clip_rect.y, clip_rect.yy);
}


/**
 * Zeichnet rohe Pixeldaten
 */
void display_array_wh(KOORD_VAL xp, KOORD_VAL yp, KOORD_VAL w, KOORD_VAL h, const COLOR_VAL *arr)
{
	const int arr_w = w;
	const KOORD_VAL xoff = clip_wh(&xp, &w, clip_rect.x, clip_rect.xx);
	const KOORD_VAL yoff = clip_wh(&yp, &h, clip_rect.y, clip_rect.yy);

	if (w > 0 && h > 0) {
		PIXVAL *p = textur + xp + yp * disp_width;
		const COLOR_VAL *arr_src = arr;

		mark_rect_dirty_nc(xp, yp, xp + w - 1, yp + h - 1);

		if (xp == clip_rect.x) arr_src += xoff;
		if (yp == clip_rect.y) arr_src += yoff * arr_w;

		do {
			unsigned int ww = w;

			do {
				*p++ = specialcolormap_all_day[*arr_src++];
			} while (--ww > 0);
			arr_src += arr_w - w;
			p += disp_width - w;
		} while (--h != 0);
	}
}


// --------------------------------- text rendering stuff ------------------------------


int	display_set_unicode(int use_unicode)
{
	return has_unicode = (use_unicode != 0);
}


bool display_load_font(const char* fname)
{
	font_type fnt;
	if (load_font(&fnt, fname)) {
		free(large_font.screen_width);
		free(large_font.char_data);
		large_font = fnt;
		large_font_height = large_font.height;
		return true;
	}
	else {
		return false;
	}
}


// unicode save moving in strings
size_t get_next_char(const char* text, size_t pos)
{
	if (has_unicode) {
		return utf8_get_next_char((const utf8*)text, pos);
	}
	else {
		return pos + 1;
	}
}


long get_prev_char(const char* text, long pos)
{
	if (pos <= 0) {
		return 0;
	}
	if (has_unicode) {
		return utf8_get_prev_char((const utf8*)text, pos);
	} else {
		return pos - 1;
	}
}


KOORD_VAL display_get_char_width(utf16 c)
{
	KOORD_VAL w = large_font.screen_width[c];
	if (w == 0) w = large_font.screen_width[0];
	return w;
}


/**
 * For the next logical character in the text, returns the character code
 * as well as retrieves the char byte count and the screen pixel width
 * CAUTION : The text pointer advances to point to the next logical character
 * @author Knightly
 */
unsigned short get_next_char_with_metrics(const char* &text, unsigned char &byte_length, unsigned char &pixel_width)
{
	size_t len;
	unsigned short char_code;
	if(  has_unicode  ) {
		len = 0;
		char_code = utf8_to_utf16((const utf8 *)text, &len);
	}
	else {
		len = 1;
		char_code = (unsigned char)(*text);
	}
	if(  char_code==0  ) {
		// case : end of text reached -> do not advance text pointer
		byte_length = 0;
		pixel_width = 0;
	}
	else {
		text += len;
		byte_length = len;
		if(  char_code>=large_font.num_chars  ||  (pixel_width=large_font.screen_width[char_code])==0  ) {
			// default width for missing characters
			pixel_width = large_font.screen_width[0];
		}
	}
	return char_code;
}


/**
 * For the previous logical character in the text, returns the character code
 * as well as retrieves the char byte count and the screen pixel width
 * CAUTION : The text pointer recedes to point to the previous logical character
 * @author Knightly
 */
unsigned short get_prev_char_with_metrics(const char* &text, const char *const text_start, unsigned char &byte_length, unsigned char &pixel_width)
{
	if(  text<=text_start  ) {
		// case : start of text reached or passed -> do not move the pointer backwards
		byte_length = 0;
		pixel_width = 0;
		return 0;
	}

	unsigned short char_code;
	if(  has_unicode  ) {
		// determine the start of the previous logical character
		do {
			--text;
		} while (  text>text_start  &&  (*text & 0xC0)==0x80  );

		size_t len = 0;
		char_code = utf8_to_utf16((const utf8 *)text, &len);
		byte_length = len;
	}
	else {
		--text;
		char_code = (unsigned char)(*text);
		byte_length = 1;
	}
	if(  char_code>=large_font.num_chars  ||  (pixel_width=large_font.screen_width[char_code])==0  ) {
		// default width for missing characters
		pixel_width = large_font.screen_width[0];
	}
	return char_code;
}


/* proportional_string_width with a text of a given length
 * extended for universal font routines with unicode support
 * @author Volker Meyer
 * @date  15.06.2003
 * @author prissi
 * @date 29.11.04
 */
int display_calc_proportional_string_len_width(const char* text, size_t len)
{
	const font_type* const fnt = &large_font;
	unsigned int c, width = 0;
	int w;

#ifdef UNICODE_SUPPORT
	if (has_unicode) {
		unsigned short iUnicode;
		size_t	iLen = 0;

		// decode char; Unicode is always 8 pixel (so far)
		while (iLen < len) {
			iUnicode = utf8_to_utf16((utf8 const*)text + iLen, &iLen);
			if (iUnicode == 0) {
				return width;
			}
			else if(iUnicode>=fnt->num_chars  ||  (w = fnt->screen_width[iUnicode])==0  ) {
				// default width for missing characters
				w = fnt->screen_width[0];
			}
			width += w;
		}
	} else {
#endif
		uint8 char_width;
		while (*text!=0  &&  len>0) {
			c = (unsigned char)*text;
			if(  c>=fnt->num_chars  ||  (char_width=fnt->screen_width[c])==0  ) {
				// default width for missing characters
				char_width = fnt->screen_width[0];
			}
			width += char_width;
			text++;
			len--;
		}
#ifdef UNICODE_SUPPORT
	}
#endif
	return width;
}


/* @ see get_mask() */
static const unsigned char byte_to_mask_array[9] = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00 };

/* Helper: calculates mask for clipping *
 * Attention: xL-xR must be <=8 !!!
 * @author priss
 * @date  29.11.04
 */
static unsigned char get_h_mask(const int xL, const int xR, const int cL, const int cR)
{
	// do not mask
	unsigned char mask;

	// check, if there is something to display
	if (xR <= cL || xL >= cR) return 0;
	mask = 0xFF;
	// check for left border
	if (xL < cL && xR > cL) {
		// Left border clipped
		mask = byte_to_mask_array[cL - xL];
	}
	// check for right border
	if (xL < cR && xR > cR) {
		// right border clipped
		mask &= ~byte_to_mask_array[cR - xL];
	}
	return mask;
}


/*
 * len parameter added - use -1 for previous bvbehaviour.
 * completely renovated for unicode and 10 bit width and variable height
 * @author Volker Meyer, prissi
 * @date  15.06.2003, 2.1.2005
 */
int display_text_proportional_len_clip(KOORD_VAL x, KOORD_VAL y, const char* txt, int flags, const PLAYER_COLOR_VAL color_index, long len)
{
	const font_type* const fnt = &large_font;
	KOORD_VAL cL, cR, cT, cB;
	uint32 c;
	size_t iTextPos = 0; // pointer on text position: prissi
	int char_width_1, char_width_2; // 1 is char only, 2 includes room
	int screen_pos;
	const uint8 *char_data;
	const uint8 *p;
	KOORD_VAL yy = y + fnt->height;
	KOORD_VAL x0;	// store the inital x (for dirty marking)
	KOORD_VAL y0, y_offset, char_height;	// real y for display with clipping
	bool v_clip;
	unsigned char mask1, mask2;	// for horizontal clipping
	const PIXVAL color = specialcolormap_all_day[color_index & 0xFF];
#ifndef USE_C
	// faster drawing with assembler
	const uint32 color2 = (color << 16) | color;
#endif

	// TAKE CARE: Clipping area may be larger than actual screen size ...
	if (flags & DT_CLIP) {
		cL = clip_rect.x;
		cR = clip_rect.xx;
		cT = clip_rect.y;
		cB = clip_rect.yy;
	} else {
		cL = 0;
		cR = disp_width;
		cT = 0;
		cB = disp_height;
	}
	// don't know len yet ...
	if (len < 0) len = 0x7FFF;

	// adapt x-coordinate for alignment
	switch (flags & ALIGN_MASK) {
		case ALIGN_LEFT:
			// nothing to do
			break;

		case ALIGN_MIDDLE:
			x -= display_calc_proportional_string_len_width(txt, len) / 2;
			break;

		case ALIGN_RIGHT:
			x -= display_calc_proportional_string_len_width(txt, len);
			break;
	}

	// still something to display?
	if (x >= cR || y >= cB || y + fnt->height <= cT) {
		// nothing to display
		return 0;
	}

	// x0 contains the startin x
	x0 = x;
	y0 = y;
	y_offset = 0;
	char_height = fnt->height;
	v_clip = false;
	// calculate vertical y clipping parameters
	if (y < cT) {
		y0 = cT;
		y_offset = cT - y;
		v_clip = TRUE;
	}
	if (yy > cB) {
		char_height -= yy - cB;
		v_clip = TRUE;
	}

	// big loop, char by char
	while (iTextPos < (size_t)len  &&  txt[iTextPos] != 0) {
		int h;
		uint8 char_yoffset;

#ifdef UNICODE_SUPPORT
		// decode char
		if (has_unicode) {
			c = utf8_to_utf16((utf8 const*)txt + iTextPos, &iTextPos);
		} else {
#endif
			c = (unsigned char)txt[iTextPos++];
#ifdef UNICODE_SUPPORT
		}
#endif
		// print unknown character?
		if (c >= fnt->num_chars || fnt->screen_width[c] == 0) {
			c = 0;
		}

		// get the data from the font
		char_data = fnt->char_data + CHARACTER_LEN * c;
		char_width_1 = char_data[CHARACTER_LEN-1];
		char_yoffset = (sint8)char_data[CHARACTER_LEN-2];
		char_width_2 = fnt->screen_width[c];
		if (char_width_1>8) {
			mask1 = get_h_mask(x, x + 8, cL, cR);
			// we need to double mask 2, since only 2 Bits are used
			mask2 = get_h_mask(x + 8, x + char_width_1, cL, cR);
			mask2 &= 0xF0;
		} else {
			// char_width_1<= 8: call directly
			mask1 = get_h_mask(x, x + char_width_1, cL, cR);
			mask2 = 0;
		}
		// do the display

		if(  y_offset>char_yoffset  ) {
			char_yoffset = y_offset;
		}
		screen_pos = (y+char_yoffset) * disp_width + x;

		p = char_data + char_yoffset;
		for (h = char_yoffset; h < char_height; h++) {
			unsigned int dat = *p++ & mask1;
			PIXVAL* dst = textur + screen_pos;

#ifdef USE_C
			if (dat != 0) {
				if (dat & 0x80) dst[0] = color;
				if (dat & 0x40) dst[1] = color;
				if (dat & 0x20) dst[2] = color;
				if (dat & 0x10) dst[3] = color;
				if (dat & 0x08) dst[4] = color;
				if (dat & 0x04) dst[5] = color;
				if (dat & 0x02) dst[6] = color;
				if (dat & 0x01) dst[7] = color;
			}
#else
			// assemble variant of the above, using table and string instructions:
			// optimized for long pipelines ...
#			include "text_pixel.c"
#endif
			screen_pos += disp_width;
		}

		// extra four bits for overwidth characters (up to 12 pixel supported for unicode)
		if (char_width_1 > 8 && mask2 != 0) {
			p = char_data + char_yoffset/2+12;
			screen_pos = (y+char_yoffset) * disp_width + x + 8;
			for (h = char_yoffset; h < char_height; h++) {
				unsigned int char_dat = *p;
				PIXVAL* dst = textur + screen_pos;
				if(h&1) {
					uint8 dat = (char_dat<<4) & mask2;
					if (dat != 0) {
						if (dat & 0x80) dst[0] = color;
						if (dat & 0x40) dst[1] = color;
						if (dat & 0x20) dst[2] = color;
						if (dat & 0x10) dst[3] = color;
					}
					p++;
				}
				else {
					uint8 dat = char_dat & mask2;
					if (dat != 0) {
						if (dat & 0x80) dst[0] = color;
						if (dat & 0x40) dst[1] = color;
						if (dat & 0x20) dst[2] = color;
						if (dat & 0x10) dst[3] = color;
					}
				}
				screen_pos += disp_width;
			}
		}
		// next char: screen width
		x += char_width_2;
	}

	if (flags & DT_DIRTY) {
		// here, because only now we know the lenght also for ALIGN_LEFT text
		mark_rect_dirty_wc(x0, y, x - 1, y + 10 - 1);
	}
	// warning: aktual len might be longer, due to clipping!
	return x - x0;
}


/**
 * Zeichnet schattiertes Rechteck
 * @author Hj. Malthaner
 */
void display_ddd_box(KOORD_VAL x1, KOORD_VAL y1, KOORD_VAL w, KOORD_VAL h, PLAYER_COLOR_VAL tl_color, PLAYER_COLOR_VAL rd_color)
{
	display_fillbox_wh(x1, y1,         w, 1, tl_color, TRUE);
	display_fillbox_wh(x1, y1 + h - 1, w, 1, rd_color, TRUE);

	h -= 2;

	display_vline_wh(x1,         y1 + 1, h, tl_color, TRUE);
	display_vline_wh(x1 + w - 1, y1 + 1, h, rd_color, TRUE);
}


void display_outline_proportional(KOORD_VAL xpos, KOORD_VAL ypos, PLAYER_COLOR_VAL text_color, PLAYER_COLOR_VAL shadow_color, const char *text, int dirty)
{
	const int flags = ALIGN_LEFT | DT_CLIP | (dirty ? DT_DIRTY : 0);
	display_text_proportional_len_clip(xpos - 1, ypos - 1 + (12 - large_font_height) / 2, text, flags, shadow_color, -1);
	display_text_proportional_len_clip(xpos + 1, ypos + 1 + (12 - large_font_height) / 2, text, flags, shadow_color, -1);
	display_text_proportional_len_clip(xpos, ypos + (12 - large_font_height) / 2, text, flags, text_color, -1);
}


void display_shadow_proportional(KOORD_VAL xpos, KOORD_VAL ypos, PLAYER_COLOR_VAL text_color, PLAYER_COLOR_VAL shadow_color, const char *text, int dirty)
{
	const int flags = ALIGN_LEFT | DT_CLIP | (dirty ? DT_DIRTY : 0);
	display_text_proportional_len_clip(xpos + 1, ypos + 1 + (12 - large_font_height) / 2, text, flags, shadow_color, -1);
	display_text_proportional_len_clip(xpos, ypos + (12 - large_font_height) / 2, text, flags, text_color, -1);
}


/**
 * Zeichnet schattiertes Rechteck
 * @author Hj. Malthaner
 */
void display_ddd_box_clip(KOORD_VAL x1, KOORD_VAL y1, KOORD_VAL w, KOORD_VAL h, PLAYER_COLOR_VAL tl_color, PLAYER_COLOR_VAL rd_color)
{
	display_fillbox_wh_clip(x1, y1,         w, 1, tl_color, TRUE);
	display_fillbox_wh_clip(x1, y1 + h - 1, w, 1, rd_color, TRUE);

	h -= 2;

	display_vline_wh_clip(x1,         y1 + 1, h, tl_color, TRUE);
	display_vline_wh_clip(x1 + w - 1, y1 + 1, h, rd_color, TRUE);
}


// if width equals zero, take default value
void display_ddd_proportional(KOORD_VAL xpos, KOORD_VAL ypos, KOORD_VAL width, KOORD_VAL hgt, PLAYER_COLOR_VAL ddd_farbe, PLAYER_COLOR_VAL text_farbe, const char *text, int dirty)
{
	int halfheight = large_font_height / 2 + 1;

	display_fillbox_wh(xpos - 2, ypos - halfheight - hgt - 1, width, 1,              ddd_farbe + 1, dirty);
	display_fillbox_wh(xpos - 2, ypos - halfheight - hgt,     width, halfheight * 2, ddd_farbe,     dirty);
	display_fillbox_wh(xpos - 2, ypos + halfheight - hgt,     width, 1,              ddd_farbe - 1, dirty);

	display_vline_wh(xpos - 2,         ypos - halfheight - hgt - 1, halfheight * 2 + 1, ddd_farbe + 1, dirty);
	display_vline_wh(xpos + width - 3, ypos - halfheight - hgt - 1, halfheight * 2 + 1, ddd_farbe - 1, dirty);

	display_text_proportional_len_clip(xpos + 2, ypos - halfheight + 1, text, ALIGN_LEFT, text_farbe, -1);
}


/**
 * display text in 3d box with clipping
 * @author: hsiegeln
 */
void display_ddd_proportional_clip(KOORD_VAL xpos, KOORD_VAL ypos, KOORD_VAL width, KOORD_VAL hgt, PLAYER_COLOR_VAL ddd_farbe, PLAYER_COLOR_VAL text_farbe, const char *text, int dirty)
{
	int halfheight = large_font_height / 2 + 1;

	display_fillbox_wh_clip(xpos - 2, ypos - halfheight - 1 - hgt, width, 1,              ddd_farbe + 1, dirty);
	display_fillbox_wh_clip(xpos - 2, ypos - halfheight - hgt,     width, halfheight * 2, ddd_farbe,     dirty);
	display_fillbox_wh_clip(xpos - 2, ypos + halfheight - hgt,     width, 1,              ddd_farbe - 1, dirty);

	display_vline_wh_clip(xpos - 2,         ypos - halfheight - 1 - hgt, halfheight * 2 + 1, ddd_farbe + 1, dirty);
	display_vline_wh_clip(xpos + width - 3, ypos - halfheight - 1 - hgt, halfheight * 2 + 1, ddd_farbe - 1, dirty);

	display_text_proportional_len_clip(xpos + 2, ypos - 5 + (12 - large_font_height) / 2, text, ALIGN_LEFT | DT_CLIP, text_farbe, -1);
}


/**
 * Zeichnet einen mehrzeiligen Text
 * @author Hj. Malthaner
 *
 * Better performance without copying text!
 * @author Volker Meyer
 * @date  15.06.2003
 */
void display_multiline_text(KOORD_VAL x, KOORD_VAL y, const char *buf, PLAYER_COLOR_VAL color)
{
	if (buf != NULL && *buf != '\0') {
		const char *next;

		do {
			next = strchr(buf, '\n');
			display_text_proportional_len_clip(
				x, y, buf,
				ALIGN_LEFT | DT_DIRTY | DT_CLIP, color,
				next != NULL ? (int)(size_t)(next - buf) : -1
			);
			buf = next + 1;
			y += LINESPACE;
		} while (next != NULL);
	}
}


/**
 * zeichnet linie von x,y nach xx,yy
 * von Hajo
 **/
void display_direct_line(const KOORD_VAL x, const KOORD_VAL y, const KOORD_VAL xx, const KOORD_VAL yy, const PLAYER_COLOR_VAL color)
{
	int i, steps;
	int xp, yp;
	int xs, ys;

	const int dx = xx - x;
	const int dy = yy - y;
	const PIXVAL colval = specialcolormap_all_day[color & 0xFF];

	steps = (abs(dx) > abs(dy) ? abs(dx) : abs(dy));
	if (steps == 0) steps = 1;

	xs = (dx << 16) / steps;
	ys = (dy << 16) / steps;

	xp = x << 16;
	yp = y << 16;

	for (i = 0; i <= steps; i++) {
		display_pixel(xp >> 16, yp >> 16, colval);
		xp += xs;
		yp += ys;
	}
}


/**
 * Zeichnet eine Fortschrittsanzeige
 * @author Hj. Malthaner
 */
static const char *progress_text=NULL;


void display_set_progress_text(const char *t)
{
	progress_text = t;
}


// draws a progress bar and flushes the display
void display_progress(int part, int total)
{
	const int width=disp_width/2;
	part = (part*width)/total;

	dr_prepare_flush();

	// outline
	display_ddd_box(width/2-2, disp_height/2-9, width+4, 20, COL_GREY6, COL_GREY4);
	display_ddd_box(width/2-1, disp_height/2-8, width+2, 18, COL_GREY4, COL_GREY6);

	// inner
	display_fillbox_wh(width/2, disp_height/2-7, width, 16, COL_GREY5, TRUE);

	// progress
	display_fillbox_wh(width/2, disp_height/2-5, part, 12, COL_BLUE, TRUE);

	if(progress_text) {
		display_proportional(width,disp_height/2-4,progress_text,ALIGN_MIDDLE,COL_WHITE,0);
	}
	dr_flush();
}


// ------------------- other support routines that actually interface with the OS -----------------


/**
 * copies only the changed areas to the screen using the "tile dirty buffer"
 * To get large changes, actually the current and the previous one is used.
 */
void display_flush_buffer(void)
{
	int x, y;
	unsigned char* tmp;

#ifdef USE_SOFTPOINTER
	if (softpointer != -1) {
		ex_ord_update_mx_my();
		display_color_img(standard_pointer, sys_event.mx, sys_event.my, 0, false, true);
	}
	else {
		// crosshair, if nowthing there ...
		display_fb_internal( sys_event.mx-1, sys_event.my-3, 3, 7, COL_WHITE, 1, 0, disp_width, 0, disp_height);
		display_fb_internal( sys_event.mx-3, sys_event.my-1, 7, 3, COL_WHITE, 1, 0, disp_width, 0, disp_height);
		display_direct_line( sys_event.mx-2, sys_event.my, sys_event.mx+2, sys_event.my, COL_BLACK );
		display_direct_line( sys_event.mx, sys_event.my-2, sys_event.mx, sys_event.my+2, COL_BLACK );
	}
	old_my = sys_event.my;
#endif

#ifdef DEBUG_FLUSH_BUFFER
	for (y = 0; y < tile_lines - 1; y++) {
		x = 0;

		do {
			if (is_tile_dirty(x, y)) {
				const int xl = x;
				do {
					x++;
				} while(x < tiles_per_line && is_tile_dirty(x, y));

				display_vline_wh((xl << DIRTY_TILE_SHIFT) - 1, y << DIRTY_TILE_SHIFT, DIRTY_TILE_SIZE, 80, FALSE);
				display_vline_wh(x << DIRTY_TILE_SHIFT, y << DIRTY_TILE_SHIFT, DIRTY_TILE_SIZE, 80, FALSE);
				display_fillbox_wh(xl << DIRTY_TILE_SHIFT, y << DIRTY_TILE_SHIFT, (x - xl) << DIRTY_TILE_SHIFT, 1, 80, FALSE);
				display_fillbox_wh(xl << DIRTY_TILE_SHIFT, (y << DIRTY_TILE_SHIFT) + DIRTY_TILE_SIZE - 1, (x - xl) << DIRTY_TILE_SHIFT, 1, 80, FALSE);
			}
			x++;
		} while (x < tiles_per_line);
	}
	dr_textur(0, 0, disp_actual_width, disp_height);
#else
	for (y = 0; y < tile_lines; y++) {
		x = 0;

		do {
			if (is_tile_dirty(x, y)) {
				const int xl = x;
				do {
					x++;
				} while (x < tiles_per_line && is_tile_dirty(x, y));

				dr_textur(xl << DIRTY_TILE_SHIFT, y << DIRTY_TILE_SHIFT, (x - xl) << DIRTY_TILE_SHIFT, DIRTY_TILE_SIZE);
			}
			x++;
		} while (x < tiles_per_line);
	}
#endif

	// swap tile buffers
	tmp = tile_dirty_old;
	tile_dirty_old = tile_dirty;
	tile_dirty = tmp;
	MEMZERON(tile_dirty, tile_buffer_length);
}


/**
 * Bewegt Mauszeiger
 * @author Hj. Malthaner
 */
void display_move_pointer(KOORD_VAL dx, KOORD_VAL dy)
{
	move_pointer(dx, dy);
}


/**
 * Schaltet Mauszeiger sichtbar/unsichtbar
 * @author Hj. Malthaner
 */
void display_show_pointer(int yesno)
{
#ifdef USE_SOFTPOINTER
	softpointer = yesno;
#else
	show_pointer(yesno);
#endif
}


/**
 * mouse pointer image
 * @author prissi
 */
void display_set_pointer(int pointer)
{
	standard_pointer = pointer;
}


/**
 * mouse pointer image
 * @author prissi
 */
void display_show_load_pointer(int loading)
{
#ifdef USE_SOFTPOINTER
	softpointer = !loading;
#else
	set_pointer(loading);
#endif
}


/**
 * Holt Maus X-Position
 * @author Hj. Malthaner
 */
int get_maus_x(void)
{
	return sys_event.mx;
}


/**
 * Holt Maus y-Position
 * @author Hj. Malthaner
 */
int get_maus_y(void)
{
	return sys_event.my;
}


/**
 * Initialises the graphics module
 * @author Hj. Malthaner
 */
int simgraph_init(KOORD_VAL width, KOORD_VAL height, int full_screen)
{
	int i;

	disp_actual_width = width;
	disp_height = height;

	// get real width from os-dependent routines
	disp_width = dr_os_open(width, height, 16, full_screen);
	if(  disp_width>0  ) {


		textur = dr_textur_init();

		// init, load, and check fonts
		large_font.screen_width = NULL;
		large_font.char_data = NULL;
		display_load_font(FONT_PATH_X "prop.fnt");
	}
	else {
		puts("Error  : can't open window!");
		exit(-1);
	}

	// allocate dirty tile flags
	tiles_per_line     = (disp_width  + DIRTY_TILE_SIZE - 1) / DIRTY_TILE_SIZE;
	tile_lines         = (disp_height + DIRTY_TILE_SIZE - 1) / DIRTY_TILE_SIZE;
	tile_buffer_length = (tile_lines * tiles_per_line + 7) / 8;

	tile_dirty     = MALLOCN(unsigned char, tile_buffer_length);
	tile_dirty_old = MALLOCN(unsigned char, tile_buffer_length);

	memset(tile_dirty,     255, tile_buffer_length);
	memset(tile_dirty_old, 255, tile_buffer_length);

	// init player colors
	for(i=0;  i<MAX_PLAYER_COUNT;  i++  ) {
		player_offsets[i][0] = i*8;
		player_offsets[i][1] = i*8+24;
	}

	display_set_clip_wh(0, 0, disp_width, disp_height);

	// Hajo: Calculate daylight rgbmap and save it for unshaded tile drawing
	display_day_night_shift(0);
	memcpy(specialcolormap_all_day, specialcolormap_day_night, 256 * sizeof(PIXVAL));
	memcpy(rgbmap_all_day, rgbmap_day_night, RGBMAPSIZE * sizeof(PIXVAL));

	// find out bit depth
	{
		uint32 c = get_system_color( 0, 255, 0 );
		while((c&1)==0) {
			c >>= 1;
		}
		if(c==31) {
			// 15 bit per pixel
			blend[0] = pix_blend25_15;
			blend[1] = pix_blend50_15;
			blend[2] = pix_blend75_15;
			blend_recode[0] = pix_blend_recode25_15;
			blend_recode[1] = pix_blend_recode50_15;
			blend_recode[2] = pix_blend_recode75_15;
			outline[0] = pix_outline25_15;
			outline[1] = pix_outline50_15;
			outline[2] = pix_outline75_15;
		}
		else {
			blend[0] = pix_blend25_16;
			blend[1] = pix_blend50_16;
			blend[2] = pix_blend75_16;
			blend_recode[0] = pix_blend_recode25_16;
			blend_recode[1] = pix_blend_recode50_16;
			blend_recode[2] = pix_blend_recode75_16;
			outline[0] = pix_outline25_16;
			outline[1] = pix_outline50_16;
			outline[2] = pix_outline75_16;
		}
	}

	printf("Init done.\n");
	fflush(NULL);

	return TRUE;
}


/**
 * Prueft ob das Grafikmodul schon init. wurde
 * @author Hj. Malthaner
 */
int is_display_init(void)
{
	return textur != NULL  &&  large_font.num_chars>0;
}


/**
 * Schliest das Grafikmodul
 * @author Hj. Malthaner
 */
int simgraph_exit()
{
	guarded_free(tile_dirty);
	guarded_free(tile_dirty_old);
	display_free_all_images_above(0);
	guarded_free(images);

	tile_dirty = tile_dirty_old = NULL;
	images = NULL;

	return dr_os_close();
}


/* changes display size
 * @author prissi
 */
void simgraph_resize(KOORD_VAL w, KOORD_VAL h)
{
	disp_actual_width = max( 16, w );
	if(  h<=0  ) {
		h = 64;
	}
	// only resize, if internal values are different
	if (disp_width != w || disp_height != h) {
		KOORD_VAL new_width = dr_textur_resize(&textur, w, h, 16);
		if(  new_width!=disp_width  ||  disp_height != h) {

			disp_width = new_width;
			disp_height = h;

			guarded_free(tile_dirty);
			guarded_free(tile_dirty_old);

			tiles_per_line     = (disp_width  + DIRTY_TILE_SIZE - 1) / DIRTY_TILE_SIZE;
			tile_lines         = (disp_height + DIRTY_TILE_SIZE - 1) / DIRTY_TILE_SIZE;
			tile_buffer_length = (tile_lines * tiles_per_line + 7) / 8;

			tile_dirty     = MALLOCN(unsigned char, tile_buffer_length);
			tile_dirty_old = MALLOCN(unsigned char, tile_buffer_length);

			memset(tile_dirty,     255, tile_buffer_length);
			memset(tile_dirty_old, 255, tile_buffer_length);

			display_set_clip_wh(0, 0, disp_actual_width, disp_height);
		}
	}
}


/**
 * Speichert Screenshot
 * @author Hj. Malthaner
 */
void display_snapshot()
{
	static int number = 0;

	char buf[80];

#ifdef _WIN32
	mkdir(SCRENSHOT_PATH);
#else
	mkdir(SCRENSHOT_PATH, 0700);
#endif

	// find the first not used screenshot image
	do {
		sprintf(buf, SCRENSHOT_PATH_X "simscr%02d.png", number);
		if(access(buf, W_OK) == -1) {
			sprintf(buf, SCRENSHOT_PATH_X "simscr%02d.bmp", number);
		}
		number ++;
	} while (access(buf, W_OK) != -1);
	sprintf(buf, SCRENSHOT_PATH_X "simscr%02d.bmp", number-1);

	dr_screenshot(buf);
}
