#define DEBUG 1
// #define DEBUG_HARD 42000
#define CROP 25
// Rand, der nicht zum Farblernen verwendet wird (Prozent rundum)
#define CMY_ANNOY 1
// CMY unfair behandeln...
#define WHITE 192
// ab dort wirds als W betrachtet (64..255 sinnvoll)
#define BLACK 96
// bis dort S (0..100 oder so)
#define Q 5
// soviele LSB werden ignoriert (0..7 sinnvoll)
#define REVIDENCE 1.4
#define GEVIDENCE 1.3
#define BEVIDENCE 1.2
// Schwelle fuer z.B. r / avg(g,b), um Rot zu erkennen etc.

#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
{
#define QQ (256>>Q)
int x,y,n,count;
int xcrop,ycrop,anzpixel,linebytes;
unsigned /* char */ int r,g,b,R,G,B;
float rguess,gguess,bguess;
unsigned char * buf2;
int rhist[QQ],ghist[QQ],bhist[QQ];
#ifdef DEBUG
  printf("Reducing colors... %p -> %dx%d\n",buf,xsize,ysize);
#endif

xcrop = (CROP * xsize) / 100;
ycrop = (CROP * ysize) / 100;
xcrop = ((xcrop*3) > xsize) ? 0 : xcrop;
ycrop = ((ycrop*3) > ysize) ? 0 : ycrop;
anzpixel = xsize * ysize;
linebytes = xsize * 3;

r=g=b=255; R=G=B=0;
for (n=0;n<QQ;n++) { rhist[n]=ghist[n]=bhist[n]=0; };

buf2 = buf;   // Histogramm
for (y=ycrop;y<(ysize-ycrop);y++)
{ buf2 = buf + (linebytes * y) + (3*xcrop);
  for (x=xcrop;x<(xsize-xcrop);x++)
  { b=buf2[0]; g=buf2[1]; r=buf2[2]; buf2 += 3;
    if (((r<WHITE) || (g<WHITE) || (b<WHITE)) &&
        ((r>BLACK) || (g>BLACK) || (b>BLACK)))   // ignoriere S/W!
    { rhist[r >> Q]++; ghist[g >> Q]++; bhist[b >> Q]++; };
  };
};

count=0;                     
for (n=0;n<QQ;n++) 
{ count = (count > rhist[n]) ? count : rhist[n];
  count = (count > ghist[n]) ? count : ghist[n];
  count = (count > bhist[n]) ? count : bhist[n];  };
count >>= 8;  count = (count) ? count : 1;
for (n=0;n<QQ;n++)  // normalisieren
{ rhist[n]= rhist[n]/count; rhist[n]= (rhist[n]>255) ? 255 : rhist[n];
  ghist[n]= ghist[n]/count; ghist[n]= (ghist[n]>255) ? 255 : ghist[n];
  bhist[n]= bhist[n]/count; bhist[n]= (bhist[n]>255) ? 255 : bhist[n]; };
  
#ifdef DEBUG
  buf2 = buf;                   // Histogramm einblenden
  if ((xsize>148) && (ysize>20))
  { buf2 = buf + (linebytes * 10) + 30;
    for (x=0;x<QQ;x++) // R
    { buf2[0]=buf2[3]=0; buf2[1]=buf2[4]=0; buf2[2]=buf2[5]=rhist[x]; 
      for (y=0;y<6;y++) { buf2[y+linebytes]= (x<(WHITE>>Q)) ? buf2[y] : 255; };
      buf2 += 6; };
    buf2 = buf + (linebytes * 13) + 30;
    for (x=0;x<QQ;x++) // G 
    { buf2[0]=buf2[3]=0; buf2[2]=buf2[5]=0;  buf2[1]=buf2[4]=ghist[x]; 
      for (y=0;y<6;y++) { buf2[y+linebytes]= (x<(WHITE>>Q)) ? buf2[y] : 255; };
      buf2 += 6; };
    buf2 = buf + (linebytes * 16) + 30;
    for (x=0;x<QQ;x++) // B
    { buf2[1]=buf2[4]=0; buf2[2]=buf2[5]=0;  buf2[0]=buf2[3]=bhist[x]; 
      for (y=0;y<6;y++) { buf2[y+linebytes]= (x<(WHITE>>Q)) ? buf2[y] : 255; };
      buf2 += 6; };
  };
#endif
  
r=g=b=0; R=G=B=255;
#ifdef DEBUG
  printf("R: ");   for (n=0;n<QQ;n++) printf("%3d ",rhist[n]);
  printf("\nG: "); for (n=0;n<QQ;n++) printf("%3d ",ghist[n]);
  printf("\nB: "); for (n=0;n<QQ;n++) printf("%3d ",bhist[n]);
  printf("\nRGB range is r=%d..%d g=%d..%d b=%d..%d\n",r,R,g,G,b,B);
#endif

buf2 = buf;            // Farbreduktion
for (y=0;y<ysize;y++)
{ for (x=0;x<xsize;x++)
  { b=buf2[0]; g=buf2[1]; r=buf2[2];
    if (((r<WHITE) || (g<WHITE) || (b<WHITE)) &&
        ((r>BLACK) || (g>BLACK) || (b>BLACK)))   // ignoriere S/W!
    { n=0;
      rguess = 1.0 * r / (0.5 * (g+b));
      gguess = 1.0 * g / (0.5 * (r+b)); 
      bguess = 1.0 * b / (0.5 * (r+g));
      if (rguess>REVIDENCE) n=1;
      if (gguess>GEVIDENCE) n=2;
      if (bguess>BEVIDENCE) n=3;
      if ( ((rguess>REVIDENCE) && (gguess>GEVIDENCE)) ||
           ((gguess>GEVIDENCE) && (bguess>BEVIDENCE)) ||
           ((rguess>REVIDENCE) && (bguess>BEVIDENCE)) )
         /* then */ n=0;
      r=g=b=0;
      if (n==1) r=255; 
      if (n==2) g=255; 
      if (n==3) b=255;
      if ((r | b | g) != 255) r=g=b=64;
      buf2[0]=b; buf2[1]=g; buf2[2]=r;           // 2:3-Mehrheits-Trick
    } else
    { buf2[0]=buf2[1]=buf2[2]= (r>WHITE) ? 255 : 0; };
    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

if ( (xsize*ysize*3)>(fsize-(18+mapped[0])) )
 { printf("wrong header in tga, giving up\n"); return 1; };
#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");
#ifndef DEBUG
if (!result) 
 { unlink(quelle); } else 
 { printf("convert back failed, will keep %s\n",quelle); };
#endif

return result;
};
