/* vim:expandtab:ts=2 sw=2:
*/
/*  Grafx2 - The Ultimate 256-color bitmap paint program
    Copyright 2008 Yves Rizoud
    Copyright 2008 Franck Charlet
    Copyright 2007 Adrien Destugues
    Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud)
    Grafx2 is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; version 2
    of the License.
    Grafx2 is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Grafx2; if not, see 
*/
#include 
#include 
#include 
#include 
#include "struct.h"
#include "sdlscreen.h"
#include "global.h"
#include "errors.h"
#include "buttons.h"
#include "engine.h"
#include "misc.h"
#include "keyboard.h"
#include "sdlscreen.h"
#include "windows.h"
#include "palette.h"
#include "input.h"
#include "graph.h"
///Count used palette indexes in the whole picture
///Return the total number of different colors
///Fill in "usage" with the count for each color
word Count_used_colors(dword* usage)
{
	int nb_pixels = 0;
	Uint8* current_pixel = Main_screen;
	Uint8 color;
	word nb_colors = 0;
	int i;
	for (i = 0; i < 256; i++) usage[i]=0;
	// Compute total number of pixels in the picture
	nb_pixels = Main_image_height * Main_image_width;
	// For each pixel in picture
	for (i = 0; i < nb_pixels; i++)
	{
		color=*current_pixel; // get color in picture for this pixel
		usage[color]++; // add it to the counter
		// go to next pixel
		current_pixel++;
	}
	// count the total number of unique used colors
	for (i = 0; i < 256; i++)
	{
		if (usage[i]!=0)
			nb_colors++;
	}
	return nb_colors;
}
/// Same as ::Count_used_colors, but use a block screen memory instead of
/// picture data. Used to count colors in the loading screen.
word Count_used_colors_screen_area(dword* usage, word start_x, word start_y,
	word width, word height)
{
	Uint8 color;
	word x, y;
	word nb_colors = 0;
	int i;
	// Init usage table
	for (i = 0; i < 256; i++) usage[i]=0;
	// For each pixel in screen area
	for (y = 0; y < height; y++)
	{
		for (x = 0; x < width; x++)
		{
			// Get color in screen memory
			color=*(Screen_pixels+((start_x + x)+(start_y + y) * Screen_width
				* Pixel_height) * Pixel_width);
			usage[color]++; //Un point de plus pour cette couleur
		}
	}
	//On va maintenant compter dans la table les couleurs utilisées:
	for (i = 0; i < 256; i++)
	{
		if (usage[i]!=0)
			nb_colors++;
	}
	return nb_colors;
}
/// Same as ::Count_used_colors, but for a given rectangle in the picture only.
/// Used bu the C64 block constraint checker.
word Count_used_colors_area(dword* usage, word start_x, word start_y,
	word width, word height)
{
	Uint8 color;
	word x, y;
	word nb_colors = 0;
	int i;
	// Init usage table
	for (i = 0; i < 256; i++) usage[i]=0;
	// On parcourt l'écran courant pour compter les utilisations des couleurs
	for (y = 0; y < height; y++)
	{
		for (x = 0; x < width; x++)
		{
			// Get color from picture
			color=*(Main_screen+((start_x + x)+(start_y + y)*Main_image_width));
			usage[color]++; //Un point de plus pour cette couleur
		}
	}
	//On va maintenant compter dans la table les couleurs utilisées:
	for (i = 0; i < 256; i++)
	{
		if (usage[i]!=0)
			nb_colors++;
	}
	return nb_colors;
}
void Set_palette(T_Palette palette)
{
	register int i;
	SDL_Color PaletteSDL[256];
	for(i=0;i<256;i++)
	{
		PaletteSDL[i].r=(palette[i].R=Round_palette_component(palette[i].R));
		PaletteSDL[i].g=(palette[i].G=Round_palette_component(palette[i].G));
		PaletteSDL[i].b=(palette[i].B=Round_palette_component(palette[i].B));
	}
	SDL_SetPalette(Screen_SDL, SDL_PHYSPAL | SDL_LOGPAL, PaletteSDL,0,256);
}
void Set_color(byte color, byte red, byte green, byte blue)
{
	SDL_Color comp;
	comp.r=red;
	comp.g=green;
	comp.b=blue;
	SDL_SetPalette(Screen_SDL, SDL_PHYSPAL | SDL_LOGPAL, &comp, color, 1);
}
void Wait_end_of_click(void)
{
	// On désactive tous les raccourcis clavier
	while(Mouse_K) if(!Get_input()) SDL_Delay(20);
}
void Hide_current_image_with_stencil(byte color, byte * stencil)
	//Effacer l'image courante avec une certaine couleur en mode Stencil
{
	int nb_pixels=0; //ECX
	//al=color
	//edi=Screen_pixels
	byte* pixel=Main_backups->Pages->Image[Main_current_layer];
	int i;
	nb_pixels=Main_image_height*Main_image_width;
	for(i=0;iPages->Image[Main_current_layer],
			color ,
			Main_image_width * Main_image_height
		  );
}
void Init_chrono(dword delay)
	// Démarrer le chrono
{
	Timer_delay = delay;
	Timer_start = SDL_GetTicks()/55;
	return;
}
void Pixel_in_brush (word x, word y, byte color)
{
	*(Brush + y * Brush_width + x)=color;
}
byte Read_pixel_from_brush (word x, word y)
{
	return *(Brush + y * Brush_width + x);
}
void Replace_a_color(byte old_color, byte new_color)
{
  word x;
  word y;
  
  // Update all pixels
	for (y=0; y0;dx--)
	{
		// Pour chaque pixel
		for(cx=width;cx>0;cx--)
		{
			*buffer = conversion_table[*buffer];
			buffer++;
		}
		buffer += buffer_width-width;
	}
}
void Copy_image_to_brush(short start_x,short start_y,short Brush_width,short Brush_height,word image_width)
{
    byte* src=start_y*image_width+start_x+Main_backups->Pages->Image[Main_current_layer]; //Adr départ image (ESI)
    byte* dest=Brush; //Adr dest brosse (EDI)
    int dx;
	for (dx=Brush_height;dx!=0;dx--)
		//Pour chaque ligne
	{
		// On fait une copie de la ligne
		memcpy(dest,src,Brush_width);
		// On passe à la ligne suivante
		src+=image_width;
		dest+=Brush_width;
	}
}
byte Read_pixel_from_feedback_screen (word x,word y)
{
	return *(FX_feedback_screen+y*Main_image_width+x);
}
dword Round_div(dword numerator,dword divisor)
{
	return numerator/divisor;
}
byte Effect_sieve(word x,word y)
{
	return Sieve[x % Sieve_width][y % Sieve_height];
}
void Replace_colors_within_limits(byte * replace_table)
{
	int y;
	int x;
	byte* pixel;
	// Pour chaque ligne :
	for(y = Limit_top;y <= Limit_bottom; y++)
	{
		// Pour chaque pixel sur la ligne :
		for (x = Limit_left;x <= Limit_right;x ++)
		{
			pixel = Main_backups->Pages->Image[Main_current_layer]+y*Main_image_width+x;
			*pixel = replace_table[*pixel];
		}
	}
}
byte Read_pixel_from_backup_screen (word x,word y)
{
	return *(Screen_backup + x + Main_image_width * y);
}
void Palette_256_to_64(T_Palette palette)
{
	int i;
	for(i=0;i<256;i++)
	{
		palette[i].R = palette[i].R >> 2;
		palette[i].G = palette[i].G >> 2;
		palette[i].B = palette[i].B >> 2;
	}
}
void Palette_64_to_256(T_Palette palette)
{
	int i;
	for(i=0;i<256;i++)
	{
		palette[i].R = (palette[i].R << 2)|(palette[i].R >> 4);
		palette[i].G = (palette[i].G << 2)|(palette[i].G >> 4);
		palette[i].B = (palette[i].B << 2)|(palette[i].B >> 4);
	}
}
byte Effect_interpolated_colorize  (word x,word y,byte color)
{
	// factor_a = 256*(100-Colorize_opacity)/100
	// factor_b = 256*(    Colorize_opacity)/100
	//
	// (Couleur_dessous*factor_a+color*facteur_B)/256
	//
	// On place dans ESI 3*Couleur_dessous ( = position de cette couleur dans la
	// palette des teintes) et dans EDI, 3*color.
	byte color_under = Read_pixel_from_feedback_screen(x,y);
	byte blue_under=Main_palette[color_under].B;
	byte blue=Main_palette[color].B;
	byte green_under=Main_palette[color_under].G;
	byte green=Main_palette[color].G;
	byte red_under=Main_palette[color_under].R;
	byte red=Main_palette[color].R;
	// On récupère les 3 composantes RVB
	// blue
	blue = (Factors_inv_table[blue]
			+ Factors_table[blue_under]) / 256;
	green = (Factors_inv_table[green]
			+ Factors_table[green_under]) / 256;
	red = (Factors_inv_table[red]
			+ Factors_table[red_under]) / 256;
	return Best_color(red,green,blue);
}
byte Effect_additive_colorize    (word x,word y,byte color)
{
	byte color_under = Read_pixel_from_feedback_screen(x,y);
	byte blue_under=Main_palette[color_under].B;
	byte green_under=Main_palette[color_under].G;
	byte red_under=Main_palette[color_under].R;
	byte blue=Main_palette[color].B;
	byte green=Main_palette[color].G;
	byte red=Main_palette[color].R;
	return Best_color(
			red>red_under?red:red_under,
			green>green_under?green:green_under,
			blue>blue_under?blue:blue_under);
}
byte Effect_substractive_colorize(word x,word y,byte color)
{
	byte color_under = Read_pixel_from_feedback_screen(x,y);
	byte blue_under=Main_palette[color_under].B;
	byte green_under=Main_palette[color_under].G;
	byte red_under=Main_palette[color_under].R;
	byte blue=Main_palette[color].B;
	byte green=Main_palette[color].G;
	byte red=Main_palette[color].R;
	return Best_color(
			redTimer_start) Timer_state=1;
}
void Flip_Y_lowlevel(byte *src, short width, short height)
{
	// ESI pointe sur la partie haute de la brosse
	// EDI sur la partie basse
	byte* ESI = src ;
	byte* EDI = src + (height - 1) *width;
	byte tmp;
	word cx;
	while(ESI < EDI)
	{
		// Il faut inverser les lignes pointées par ESI et
		// EDI ("Brush_width" octets en tout)
		for(cx = width;cx>0;cx--)
		{
			tmp = *ESI;
			*ESI = *EDI;
			*EDI = tmp;
			ESI++;
			EDI++;
		}
		// On change de ligne :
		// ESI pointe déjà sur le début de la ligne suivante
		// EDI pointe sur la fin de la ligne en cours, il
		// doit pointer sur le début de la précédente...
		EDI -= 2 * width; // On recule de 2 lignes
	}
}
void Flip_X_lowlevel(byte *src, short width, short height)
{
	// ESI pointe sur la partie gauche et EDI sur la partie
	// droite
	byte* ESI = src;
	byte* EDI = src + width - 1;
	byte* line_start;
	byte* line_end;
	byte tmp;
	word cx;
	while(ESI0;cx--)
		{
			tmp=*ESI;
			*ESI=*EDI;
			*EDI=tmp;
			EDI+=width;
			ESI+=width;
		}
		// On change de colonne
		// ESI > colonne suivante
		// EDI > colonne précédente
		ESI = line_start + 1;
		EDI = line_end - 1;
	}
}
// Rotate a pixel buffer 180º on itself.
void Rotate_180_deg_lowlevel(byte *src, short width, short height)
{
	// ESI pointe sur la partie supérieure de la brosse
	// EDI pointe sur la partie basse
	byte* ESI = src;
	byte* EDI = src + height*width - 1;
	// EDI pointe sur le dernier pixel de la derniere ligne
	byte tmp;
	word cx;
	// In case of odd height, the algorithm in this function would
	// miss the middle line, so we do it this way:
	if (height & 1)
	{
		Flip_X_lowlevel(src, width, height);
		Flip_Y_lowlevel(src, width, height);
		return;
	}
	while(ESI < EDI)
	{
		// On échange les deux lignes pointées par EDI et
		// ESI (Brush_width octets)
		// En même temps, on échange les pixels, donc EDI
		// pointe sur la FIN de sa ligne
		for(cx=width;cx>0;cx--)
		{
			tmp = *ESI;
			*ESI = *EDI;
			*EDI = tmp;
			EDI--; // Attention ici on recule !
			ESI++;
		}
	}
}
void Rescale(byte *src_buffer, short src_width, short src_height, byte *dst_buffer, short dst_width, short dst_height, short x_flipped, short y_flipped)
{
	int    offset,line,column;
	int    x_pos_in_brush;   // Position courante dans l'ancienne brosse
	int    y_pos_in_brush;
	int    delta_x_in_brush; // "Vecteur incrémental" du point précédent
	int    delta_y_in_brush;
	int    initial_x_pos;       // Position X de début de parcours de ligne
	// Calcul du "vecteur incrémental":
	delta_x_in_brush=(src_width<<16) * (x_flipped?-1:1) / (dst_width);
	delta_y_in_brush=(src_height<<16) * (y_flipped?-1:1) / (dst_height);
	offset=0;
	// Calcul de la valeur initiale de y_pos:
	if (y_flipped)
		y_pos_in_brush=(src_height<<16)-1; // Inversion en Y de la brosse
	else
		y_pos_in_brush=0;                // Pas d'inversion en Y de la brosse
	// Calcul de la valeur initiale de x_pos pour chaque ligne:
	if (x_flipped)
		initial_x_pos = (src_width<<16)-1; // Inversion en X de la brosse
	else
		initial_x_pos = 0;                // Pas d'inversion en X de la brosse
	// Pour chaque ligne
	for (line=0;line>16) + (y_pos_in_brush>>16)*src_width);
			// On passe à la colonne de brosse suivante:
			x_pos_in_brush+=delta_x_in_brush;
			// On passe au pixel suivant de la nouvelle brosse:
			offset++;
		}
		// On passe à la ligne de brosse suivante:
		y_pos_in_brush+=delta_y_in_brush;
	}
}
void Slider_timer(byte speed)
	//Boucle d'attente pour faire bouger les scrollbars à une vitesse correcte
{
	Uint32 end;
	byte original_mouse_k = Mouse_K;
	end = SDL_GetTicks() + speed*10;
	do
	{
		if (!Get_input()) SDL_Delay(20);
	} while (Mouse_K == original_mouse_k && SDL_GetTicks()0;y--)
	{
		// Pour chaque ligne
		memcpy(dest,src,length);
		memcpy(dest - x_offset,src+length,x_offset);
		// On passe à la ligne suivante
		dest += Main_image_width;
		src += Main_image_width;
	}
	// On vient de faire le traitement pour otutes les lignes au-dessous de y_offset
	// Maintenant on traite celles au dessus
	dest = x_offset + main_dest;
	for(y = y_offset;y>0;y--)
	{
		memcpy(dest,src,length);
		memcpy(dest - x_offset,src+length,x_offset);
		dest += Main_image_width;
		src += Main_image_width;
	}
	Update_rect(0,0,0,0);
}
void Zoom_a_line(byte* original_line, byte* zoomed_line,
		word factor, word width
		)
{
	byte color;
	word x;
	// Pour chaque pixel
	for(x=0;x
#elif defined(__macosx__) || defined(__FreeBSD__)
  #include 
#elif defined(__BEOS__) || defined(__HAIKU__)
  // sysinfo not implemented
#elif defined(__AROS__) || defined(__amigaos4__) || defined(__MORPHOS__) || defined(__amigaos__)
  #include 
#elif defined(__SKYOS__)
  #include 
#else
  #include  // sysinfo() for free RAM
#endif
// Indique quelle est la mémoire disponible
unsigned long Memory_free(void)
{
	// Memory is no longer relevant. If there is ANY problem or doubt here,
	// you can simply return 10*1024*1024 (10Mb), to make the "Pages"something
	// memory allocation functions happy.
	// However, it is still a good idea to make a proper function if you can...
	// If Grafx2 thinks the memory is full, weird things may happen. And if memory
	// ever becomes full and you're still saying there are 10MB free here, the
	// program will crash without saving any picture backup ! You've been warned...
#if defined(__WIN32__)
	MEMORYSTATUS mstt;
	mstt.dwLength = sizeof(MEMORYSTATUS);
	GlobalMemoryStatus(&mstt);
	return mstt.dwAvailPhys;
#elif defined(__macosx__) || defined(__FreeBSD__)
	int mib[2];
	int maxmem;
	size_t len;
	mib[0] = CTL_HW;
	mib[1] = HW_USERMEM;
	len = sizeof(maxmem);
	sysctl(mib,2,&maxmem,&len,NULL,0);
	return maxmem;
#elif defined(__BEOS__) || defined(__HAIKU__) || defined(__SKYOS__) || defined(__amigaos4__)
	// No  on BeOS or Haiku
	// AvailMem is misleading on os4 (os4 caches stuff in memory that you can still allocate)
#warning "There is missing code there for your platform ! please check and correct :)"
	return 10*1024*1024;
#elif defined(__AROS__) || defined(__MORPHOS__) || defined(__amigaos__)
	return AvailMem(MEMF_ANY);
#else
	struct sysinfo info;
	sysinfo(&info);
	return info.freeram*info.mem_unit;
#endif
}
// Transformer un nombre (entier naturel) en chaîne
void Num2str(dword number,char * str,byte nb_char)
{
	int index;
	for (index=nb_char-1;index>=0;index--)
	{
		str[index]=(number%10)+'0';
		number/=10;
		if (number==0)
			for (index--;index>=0;index--)
				str[index]=' ';
	}
	str[nb_char]='\0';
}
// Arrondir un nombre réel à la valeur entière la plus proche
short Round(float value)
{
	short temp=value;
	if (value>=0)
	{ if ((value-temp)>= 0.5) temp++; }
	else
	{ if ((value-temp)<=-0.5) temp--; }
	return temp;
}
// Arrondir le résultat d'une division à la valeur entière supérieure
short Round_div_max(short numerator,short divisor)
{
	if (!(numerator % divisor))
		return (numerator/divisor);
	else
		return (numerator/divisor)+1;
}
// Retourne le minimum entre deux nombres
int Min(int a,int b)
{
	return (ab)?a:b;
}
// Fonction retournant le libellé d'une mode (ex: " 320x200")
char * Mode_label(int mode)
{
	static char str[24];
	if (! Video_mode[mode].Fullscreen)
		return "window";
	sprintf(str, "%dx%d", Video_mode[mode].Width, Video_mode[mode].Height);
	return str;
}
// Trouve un mode video à partir d'une chaine: soit "window",
// soit de la forme "320x200"
// Renvoie -1 si la chaine n'est pas convertible
int Convert_videomode_arg(const char *argument)
{
	// Je suis paresseux alors je vais juste tester les libellés
	int mode_index;
	for (mode_index=0; mode_index