#define DEBUG 1
// #define DEBUG_HARD 42000
// #define __USE_GNU 42
// coz I love O_NOFOLLOW -> 0400000
#define MATCH_BIAS 250
// max. Manhattan Farbabstand fuer Match... > 384/2 ist wohl schlecht...
#define LEERRAUM 90
// ... Streifen ... (in beide Richtungen ab 128)
#define IGN_SW 1
// 0 heisst: 0..7 fuer Match testen, 1 heisst: 1..6 testen, S/W fixieren.
#define TRY_LEARNING 1
// experimentelles Feature...
#define CROP 33
// Rand, der nicht zum Farblernen verwendet wird
#define CMY_ANNOY 1
// CMY unfair behandeln...
#define ALLOW_GREY 0
// 0 oder 1: Mitte-Grau als Option lassen...
#define LIFT_BLACK 1
// 0 oder 1: Nicht-so-Dunkles auch noch als Schwarz sehen

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h> // system...
#include<string.h>
#include<errno.h> // ...
#include<fcntl.h> // open
#include<sys/stat.h> // stat
#include<sys/wait.h> // waitpid
#include<sys/types.h>
#include<sys/mman.h> // mmap msync munmap ... really usefull, also for IPC!

// --------------------------------------------------------------------

void farbreduktion(unsigned char * buf, int xsize, int ysize)
// Assemblervorschlaege ohne Jcc von AMD:
// abs: z=wert sar 31; erg=(wert ^ z) - z
// min: x -= y; sbb z,z /* wird 0 oder -1 */; z &= x; z += y
{
int x,y,n,nsieger,fehler,minfehler;
unsigned char r,g,b,R,G,B;
unsigned char * buf2;
unsigned char r8[8+ALLOW_GREY];
unsigned char g8[8+ALLOW_GREY];
unsigned char b8[8+ALLOW_GREY];

#ifdef DEBUG
  printf("Reducing colors... %p -> %dx%d\n",buf,xsize,ysize);
#endif

r=g=b=255; R=G=B=0;    // jetzt Grenzwerte suchen...
buf2 = buf;            // evtl irgendwie anders vor Ausreissern sichern?
for (y=CROP;y<(ysize-CROP);y++)
  { for (x=CROP;x<(xsize-CROP);x++)
    { b=(buf2[0]<b)?(b-1):b;
      B=(buf2[0]>B)?(B+1):B;
      buf2++;
      g=(buf2[0]<g)?(g-1):g;
      G=(buf2[0]>G)?(G+1):G;
      buf2++;
      r=(buf2[0]<r)?(r-1):r;
      R=(buf2[0]>R)?(R+1):R;
      buf2++;
    };
  };

if ( (xsize<(3*CROP)) || (ysize<(3*CROP)) )
  { r=g=b=0; R=G=B=255;    // Bild war zu klein zum Suchen...
  };
  
#ifdef DEBUG
  printf("RGB range is r=%d..%d g=%d..%d b=%d..%d\n",r,R,g,G,b,B);
#endif

b8[0]=b8[1]=b8[2]=b8[3]=b;
b8[4]=b8[5]=b8[6]=b8[7]=B;
g8[0]=g8[1]=g8[4]=g8[5]=g;
g8[2]=g8[3]=g8[6]=g8[7]=G;
r8[0]=r8[2]=r8[4]=r8[6]=r;
r8[1]=r8[3]=r8[5]=r8[7]=R;
#ifdef CMY_ANNOY
  // jetzt CMY unfair behandeln... 8-)
  r8[3]=r8[5]=r8[6]=255; g8[3]=g8[5]=g8[6]=255; b8[3]=b8[5]=b8[6]=255;
#endif
if (ALLOW_GREY)
{
  n = r+R; r8[8] = n >> 1;
  n = g+G; g8[8] = n >> 1;
  n = b+B; b8[8] = n >> 1;
};
if (LIFT_BLACK)
{
  n = r; n *= 15; n += R; r8[0] = n >> 4;
  n = g; n *= 15; n += G; g8[0] = n >> 4;
  n = b; n *= 15; n += B; b8[0] = n >> 4;
};


#ifdef TRY_LEARNING
srandom(42);                   // Farbreduktion basierend auf Manhattan distance
for (y=0;y< ((ysize*xsize)>>4) ;y++)  
                               // suche 8 passende Standardfarben
  { buf2 = buf + (3 * (random() % (xsize * ysize)) );
                               // es ist wichtig, die Stichprobe zu verteilen!
                               // eigentlich sollte auch hier CROP gelten.
    b=buf2[0]; buf2++; g=buf2[0]; buf2++; r=buf2[0]; buf2++;
    minfehler=MATCH_BIAS;      // meine Wahl, Automatik waere besser.
    nsieger=-1; 
    for (n=IGN_SW;n<(8-IGN_SW);n++)
      { fehler = abs(r-r8[n]) + abs(g-g8[n]) + abs(b-b8[n]);
        if ((fehler<minfehler)
#ifdef CMY_ANNOY
            && (n!=3) && (n!=5) && (n!=6)
#endif
           ) { nsieger = n; minfehler = fehler; };
      };
    if (ALLOW_GREY)
      { fehler = abs(r-r8[n]) + abs(g-g8[n]) + abs(b-b8[n]);
        if (fehler<minfehler) { nsieger = n; minfehler = fehler; };
      };      
    if (nsieger >= 0) // soll Lernen von Grey erlaubt sein?
    {
      n = nsieger;
#ifdef DEBUG_HARD
      printf("Match %d for: r=%d/%d g=%d/%d b=%d/%d (dist %d)\n",
        n,r,r8[n],g,g8[n],b,b8[n],minfehler);
#endif
      if ((r>r8[n]) && (r8[n]<255) && (r8[n]!=127-LEERRAUM)) r8[n]++;
      if ((g>g8[n]) && (g8[n]<255) && (g8[n]!=127-LEERRAUM)) g8[n]++;
      if ((b>b8[n]) && (b8[n]<255) && (b8[n]!=127-LEERRAUM)) b8[n]++;
      if ((r<r8[n]) && (r8[n]>0) && (r8[n]!=129+LEERRAUM)) r8[n]--;
      if ((g<g8[n]) && (g8[n]>0) && (g8[n]!=129+LEERRAUM)) g8[n]--;
      if ((b<b8[n]) && (b8[n]>0) && (b8[n]!=129+LEERRAUM)) b8[n]--;
      // andere Art von Lernfunktion waere wohl stabiler...
      // erste Idee: Verbot, 128 zu ueberqueren -> besser Mitte...
    }
    else
    {
#ifdef DEBUG_HARD
      printf("NO match for: r=%d g=%d b=%d (maxdist %d)\n",r,g,b,minfehler);
#endif
    };
  };
#ifdef DEBUG
  for (n=0;n<(8+ALLOW_GREY);n++)
  { printf("Pal_New[%d] is r=%3d g=%3d b=%3d  ",n,r8[n],g8[n],b8[n]);
    if (n & 1) { printf("\n"); };
  };
  if (ALLOW_GREY) { printf("\n"); };
#endif
#endif

buf2 = buf;            // Farbreduktion basierend auf Manhattan distance
for (y=0;y<ysize;y++)  // zu den 8 gefundenen Standardfarben
  { for (x=0;x<xsize;x++)
    { b=buf2[0]; g=buf2[1]; r=buf2[2];
      minfehler=0x77777777;
      nsieger=7;       // nix finden heisst weiss
      for (n=0;n<(8+ALLOW_GREY);n++)
      { fehler = abs(r-r8[n]) + abs(g-g8[n]) + abs(b-b8[n]);
        if ((fehler<minfehler)
#ifdef CMY_ANNOY
            && (n!=3) && (n!=5) && (n!=6)
#endif        
           ) { nsieger = n; minfehler = fehler; };
      };
      buf2[0] = (nsieger & 4) ? 255 : 0; // B
      buf2[1] = (nsieger & 2) ? 255 : 0; // G
      buf2[2] = (nsieger & 1) ? 255 : 0; // R
      if (nsieger==8) { buf2[0]=buf2[1]=buf2[2]=128; };
      buf2 += 3;
    };
  };
return;
};

// --------------------------------------------------------------------

int rufe_convert(char * rein, char * raus_mit_typ,char * opt)
{ 
  int pid,status;
#ifdef DEBUG
  printf("Converting %s -> %s\n",rein,raus_mit_typ);
#endif
  pid = fork();
  if (pid == -1) return -1;
  if (pid == 0) 
  {
    char *argv2[5];
    argv2[0] = "convert"; // mit execve statt ... execvp ganzer Pfad noetig
    argv2[1] = opt;
    argv2[2] = rein;
    argv2[3] = raus_mit_typ;
    argv2[4] = NULL;
    execvp("convert", argv2); // execve waere kernelcall
    exit(127);
  }
  for (;;) 
  {
    if (waitpid(pid, &status, 0) == -1) 
    {
      if (errno != EINTR) return -1;
     } else return status;
  };
};

// --------------------------------------------------------------------

int main(int argc,char * argv[])
{
int result,xsize,ysize;
unsigned char * mapped;
int * mapped_32;

int fd,fsize;
struct stat status;
char ziel[120];
char quelle[120];

if ((argc!=2) || (!argv[0]) || (!argv[1]) || 
    (strlen(argv[0])>=100)   || (strlen(argv[1])>=100))
 { printf("Erics ColoReduce:\nImage name is the only argument.\n"); return 1; };

strncpy(quelle,argv[1],100);
strncat(quelle,".tga",5); // Quelle: bla.tga
strncpy(ziel,"TGA:",5);
strncat(ziel,quelle,110); // Ziel: fuer convert
result = rufe_convert(argv[1],ziel,"+compress");
// +compress means do NOT compress
if (result) { printf("convert to tga failed\n"); return 1; };

if (stat(quelle,&status)) { printf("could not stat image\n"); return 1; };
fsize = status.st_size;
if (!S_ISREG(status.st_mode)) { printf("not a regular file\n"); return 1; };
#ifdef DEBUG
  printf("Image %s has size %d\n",quelle,fsize);
#endif
fd = open(quelle,O_RDWR|0400000); // 04... ist O_NOFOLLOW
if (!fd) { printf("could not open image\n"); return 1; };
mapped = mmap(NULL,fsize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
// passt mir zwar nicht, aber MAP_PRIVATE
// scheint den File nicht zu schreiben
// sondern nur das RAM... also MAP_SHARED
if (mapped <= 0) { printf("mmap failed\n"); return 1; };
close(fd);
mapped_32 = (int *)mapped;

#ifndef SPARC
if (((mapped_32[0] & 0x0ffffff00) !=0x00020000) || (mapped_32[1]) || 
    (mapped_32[2]) ||
    (mapped_32[3] & 0x0f000f000) || ((mapped_32[4] & 0x0ffff) != 24))
 { printf("this is a strange type of file, giving up\n"); return 1; };
xsize = mapped_32[3] & 0x0ffff;
ysize = mapped_32[3] >> 16;
#else
xsize = mapped[13]; xsize <<= 8; xsize += mapped[12];
ysize = mapped[15]; ysize <<= 8; ysize += mapped[14];
// restliche Tests bei Sparc weggelassen...
#endif

#ifdef DEBUG
  printf("TGA size is %dx%d (special is %d)\n",xsize,ysize,mapped[0]);
#endif

farbreduktion(mapped+18+mapped[0],xsize,ysize);

if (msync(mapped,fsize,MS_SYNC)) { printf("msync failed\n"); return 1; };
if (munmap(mapped,fsize)) { printf("munmap failed\n"); return 1; };
#ifdef DEBUG_HARD
  system("/bin/sh");
#endif

result = rufe_convert(quelle,argv[1],"-verbose");
if (!result) 
 { unlink(quelle); } else 
 { printf("convert back failed, will keep %s\n",quelle); };

return result;
};
