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

#include <vcl.h>
#include <math.h>
#include <stdio.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int net[1000][1000];
unsigned char scr[6144];
TColor colors[8] = {clBlack, clBlue, clRed, clPurple, clGreen, clNavy, clYellow, clWhite};
int frame=0;

  float u0,v0,u,v,du,dv; //24.8
  float h,hline,L,dist;
  float a,b,apb,g;
  float pi = 3.1415926536;
  //float yaperture = pi/4;
  //float xaperture = pi/4;
  int chmhgt = 64;
  int chmwid = 64;
  int iu,iv,idu,idv;
  TColor color;

#define USAGESZ 18

  int usage[USAGESZ][USAGESZ][USAGESZ][USAGESZ];

  unsigned char byte2colors[256][4];
  int bytes2colors;
  unsigned char repic[64*192];
  unsigned char rebyte[16*2*256];

  char IntToHexDig[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};

TColor c0 = clBlack;
TColor c1 = clBlue;
TColor c2 = clGray;
TColor c3 = clYellow;
TColor c4 = clWhite;
TColor texture[16][16] = {
        { c0,c0,c0,c0,c0,c0,c0,c0, c0,c0,c0,c0,c0,c0,c0,c0 },
        { c0,c2,c0,c0,c0,c0,c0,c0, c0,c0,c0,c0,c0,c0,c2,c0 },
        { c2,c3,c2,c0,c0,c0,c0,c0, c0,c0,c0,c0,c2,c3,c3,c2 },
        { c0,c2,c3,c3,c2,c2,c2,c0, c0,c0,c2,c3,c4,c3,c2,c0 },
        { c0,c0,c0,c2,c3,c3,c3,c3, c3,c3,c3,c4,c4,c2,c0,c0 },
        { c0,c0,c0,c0,c0,c0,c2,c2, c2,c0,c2,c3,c2,c0,c0,c0 },
        { c0,c0,c0,c0,c0,c0,c0,c0, c0,c0,c3,c3,c0,c0,c0,c0 },
        { c0,c0,c0,c0,c0,c0,c0,c0, c0,c2,c3,c2,c0,c0,c0,c0 },

        { c0,c0,c0,c0,c0,c0,c0,c0, c0,c3,c3,c0,c0,c0,c0,c0 },
        { c0,c0,c0,c0,c0,c0,c0,c0, c0,c3,c2,c0,c0,c0,c0,c0 },
        { c0,c0,c0,c0,c0,c0,c0,c0, c0,c3,c2,c0,c0,c0,c0,c0 },
        { c0,c0,c2,c0,c0,c0,c0,c0, c2,c3,c0,c0,c0,c0,c0,c0 },
        { c0,c2,c3,c2,c0,c0,c0,c2, c4,c3,c0,c0,c0,c0,c0,c0 },
        { c0,c0,c3,c4,c2,c2,c2,c4, c4,c2,c0,c0,c0,c0,c0,c0 },
        { c0,c0,c0,c2,c3,c3,c3,c3, c2,c0,c0,c0,c0,c0,c0,c0 },
        { c0,c0,c0,c0,c0,c0,c2,c0, c0,c0,c0,c0,c0,c0,c0,c0 }
 };

int chunkpixelnumber[16] = { //рисунок чанков
/*  0x0, 0xc, 0x2, 0xe,
  0x8, 0x4, 0xa, 0x6,
  0x3, 0xf, 0x1, 0xd,
  0xb, 0x7, 0x9, 0x5/*diamond*/
  0x7, 0xe, 0x2, 0xa,
  0x9, 0x0, 0xc, 0x4,
  0xd, 0xb, 0x6, 0x1,
  0x3, 0x5, 0x8, 0xf/*noise*/
};
int achunkpixelnumber[16] = { //смещение от кадра к кадру
//  0x0, 0xa, 0x2, 0x8,
//  0x5, 0xf, 0x7, 0xd,
//  0x4, 0xe, 0x6, 0xc,
//  0x1, 0xb, 0x3, 0x9
  0x7, 0xe, 0x2, 0xa,
  0x9, 0x0, 0xc, 0x4,
  0xd, 0xb, 0x6, 0x1,
  0x3, 0x5, 0x8, 0xf
};

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

char imul88(char a, char b)
{
  //return ((int)(a) * (int)(b)) >> 7;
  int a2 = (int)(a) * (int)(a);
  int b2 = (int)(b) * (int)(b);
  int apb = ((int)(a) + (int)(b)); //на реале надо сразу ABS, иначе переполнение
  int apb2 = (int)(apb) * (int)(apb);
  return (apb2 - a2 - b2)>>8;
}

void TForm1::drawframe()
{

  //Memo1->Clear();
/*
  u0 = 0;
  v0 = 0; //текущие координаты, над которыми располагается камера

  L = 20; //расстояние до поверхности
  a = -0.92; //угол тилта (наклона оси камеры)
  g += 0.1; //угол поворота поверхности

  for (int row=-24; row<+24; row++) {
    hline = row;
    hline = hline/(chmhgt/2); //-1..+1
    b = atan(hline); //угол текущей строки отн. оси камеры
    //Memo1->Lines->Add("b="+FloatToStr(b));
    apb = a+b;
    while (apb < -(pi/2)) {apb += (pi/2);};
    while (apb > (pi/2)) {apb -= (pi/2);}; //a+b считаем так, чтобы он был -pi/2..+pi/2, иначе прибавляем pi (попадаем на вторую копию поверхности)
    //Memo1->Lines->Add("apb="+FloatToStr(apb));

    int logcoeff = 16;
    int slogcoeff = 16;

    int ilogL = (int)(log(L)*logcoeff)&0xff;
    int ilogcosb = 0; //(int)(log(cos(b))*logcoeff);//&0xff; //-3..0..-3
    int ilogsecapb = (int)(log(1/cos(apb))*logcoeff)&0xff; if (ilogsecapb > 63) ilogsecapb = 63;
    //dist = L*(1/cos(apb)) * cos(b); //расстояние по оси строки до поверхности (минимум равен L, может быть до +oo)
    //int ilogdist = (int)(log(dist)*logcoeff)&0xff; //44..101
    int ilogdist = ilogL + ilogcosb + ilogsecapb;
     //Memo1->Lines->Add("ilogL="+IntToStr(ilogL)); //47
     //Memo1->Lines->Add("ilogcosb="+IntToStr(ilogcosb)); //-3..0..-3
     //Memo1->Lines->Add("ilogsecapb="+IntToStr(ilogsecapb)); //63..0
     //Memo1->Lines->Add("ilogdist="+IntToStr(ilogdist)); //44..110(101)
    dist = exp((float)(ilogdist)/logcoeff);
    int maxdist = 128;
    if (dist > (maxdist-1)) dist = maxdist-1;
     Memo1->Lines->Add("dist="+FloatToStr(dist));

    h = L*tan(apb); //расстояние по поверхности от UV0 до центра проекции строки (может быть -oo..+oo, по модулю брать нельзя)
    int maxh = 128; //64 заметно
    if (h > (maxh-1)) h = maxh-1;
    if (h < -maxh) h = -maxh;
    h = (float)((int)(h*1))/1;
    Memo1->Lines->Add("h="+FloatToStr(h)); //-128..+127

    //int ilogv = (int)(log(h*cos(g))*slogcoeff);
    //v = v0 + exp((float)(ilogv)/slogcoeff);

    //v = v0 + h*cos(g);
    v = v0 + imul88(h,(int)(cos(g)*128));
    //u = u0 - h*sin(g);
    u = u0 - imul88(h,(int)(sin(g)*128));

    //dv = dist*sin(g);
    dv = imul88(dist,(int)(sin(g)*128));
    //du = dist*cos(g);
    du = imul88(dist,(int)(cos(g)*128));
    dv = dv/(chmwid/2);
    du = du/(chmwid/2);
    idv = (int)(dv*32+0.5);
    idu = (int)(du*256+0.5);

    v -= (chmwid/2)*dv;
    u -= (chmwid/2)*du;
    iv = (int)(v*32);
    iu = (int)(u*256);
    for (int col=0; col<chmwid; col++) {
       color = texture[(iv>>5)&0x0f][(iu>>8)&0x0f];
       for (int y = 128+row*4; y < 128+row*4+4; y++) {
         for (int x = col*4; x < col*4+4; x++) {
           Image1->Canvas->Pixels[x][y]=color;
         };
       };
       iu += idu;
       iv += idv;
    };
  };
*/
  //srand(0);
  Memo1->Lines->Add("frame="+IntToStr(frame));
  for (int y = 0; y < 192; y++) {
    int scrrow = (y&0xc0) + ((y&0x07)<<3) + ((y&0x38)>>3);
     //Memo1->Lines->Add("scrrow="+IntToStr(scrrow));
    for (int col = 0; col < 32; col++) {
      unsigned char b = 0;
      for (int x = (col<<3); x < (col<<3)+8; x++) {
        //Image1->Canvas->Pixels[x][y]=net[y][x] * 0x010101;
        int pixelnumber = ((x&3)<<2) + ((y/2)&3);
  //Memo1->Lines->Add("pixelnumber="+IntToStr(pixelnumber));
        //if ((net[y][x]) > (random(17)){
        //if ((net[y][x]) > ((frame+achunkpixelnumber[pixelnumber])&0x0f)){
        if ((net[y][x]) > chunkpixelnumber[ //рандомно смещаем чанковую сетку по x и по y
                               ((pixelnumber+achunkpixelnumber[frame])&0x03)
                              +((pixelnumber+(achunkpixelnumber[frame]&0x0c))&0x0c)
                             ]){
          Image1->Canvas->Pixels[x][y]=clWhite;
          b = (b<<1) + 1;
        }else {
          Image1->Canvas->Pixels[x][y]=clBlack;
          b = (b<<1) + 0;
        };
      };
      scr[(scrrow<<5) + col] = b;
    };
  };

  AnsiString fn = "frame"+IntToStr(frame);
  FILE* fout;
  fout = fopen(fn.c_str(), "wb");
  fwrite(scr, 6144, 1, fout);
  fclose(fout);
  frame++;
}

AnsiString IntToHex(int i)
{
  AnsiString result = "0x";
  return result+IntToHexDig[i>>4]+IntToHexDig[i&0x0f];
}

int getusage(int maxa, int maxb, int maxc, int maxd)
{
  int u = 0;
  for (int a = maxa?(maxa-1):0; a <= maxa+1; a++) {
    //int* usage_a[USAGESZ][USAGESZ] = usage[a];
    for (int b = maxb?(maxb-1):0; b <= maxb+1; b++) {
      //int*** usage_a_b = usage_a[b];
      for (int c = maxc?(maxc-1):0; c <= maxc+1; c++) {
        int* usage_a_b_c = usage[a][b][c] + maxd;
/*        for (int d = maxd?(maxd-1):0; d <= maxd+1; d++) {
          //if ((a>=0)&&(b>=0)&&(c>=0)&&(d>=0))
          {
            u += usage_a_b_c[d];
          };
        };*/
        if (maxd) u += usage_a_b_c[-1];
        u += *usage_a_b_c++;
        u += *usage_a_b_c;
      };
    };
  };
  return u;
}

void store_destroymax(int maxa, int maxb, int maxc, int maxd)
{
  byte2colors[bytes2colors][0]=maxa;
  byte2colors[bytes2colors][1]=maxb;
  byte2colors[bytes2colors][2]=maxc;
  byte2colors[bytes2colors][3]=maxd;
  bytes2colors++;
  for (int a = maxa?(maxa-1):0; a <= maxa+1; a++) {
  for (int b = maxb?(maxb-1):0; b <= maxb+1; b++) {
  for (int c = maxc?(maxc-1):0; c <= maxc+1; c++) {
  for (int d = maxd?(maxd-1):0; d <= maxd+1; d++) {
    //if ((a>=0)&&(b>=0)&&(c>=0)&&(d>=0))
    {
      usage[a][b][c][d] = 0;
    };
  };
  };
  };
  };
}

void process(char* filename)
{
  //грузим картинку
  Graphics::TBitmap* bm = new(Graphics::TBitmap);
  //bm->LoadFromFile("bust.BMP");
  bm->LoadFromFile(filename);
  //Image1->Picture->Assign(bm);

  for (int y = 0; y < 192; y++) {
    for (int x = 0; x < 256; x++) {
      TColor color = bm->Canvas->Pixels[x][y];
      //net[y][x] = ((color & 0xff) + ((color>>8) & 0xff) + ((color>>16) & 0xff))/15;
      net[y][x] = ((color & 0xff))/15;
      if (net[y][x] > 16) net[y][x] = 16;
      if (net[y][x] == 1) net[y][x] = 0;
    };
  };

  //ищем нужный набор байтов
  bytes2colors = 0;

  for (int a = 0; a < 18; a++) {
  for (int b = 0; b < 18; b++) {
  for (int c = 0; c < 18; c++) {
  for (int d = 0; d < 18; d++) {
    usage[a][b][c][d] = 0;
  };
  };
  };
  };

  for (int y = 0; y < 192; y++) {
    for (int x = 0; x < 256; x+=4) {
      usage[net[y][x]][net[y][x+1]][net[y][x+2]][net[y][x+3]]++;
    };
  };

  for (int i = 0; i < 17; i++) {
    int u = getusage(i,i,i,i);
    int ufix = usage[i+1][i+1][i+1][i+1];
    u -= ufix;
    //Memo1->Lines->Add(IntToHex(i)+","+IntToHex(i)+","+IntToHex(i)+","+IntToHex(i)+" // "+u);
    store_destroymax(i,i,i,i);
    usage[i+1][i+1][i+1][i+1] = ufix; //чтобы не затереть следующий стандартный цвет
  };

  while (bytes2colors < 256) {
    int max = 0;
    int maxa,maxb,maxc,maxd;
    //find max
    for (int a = 0; a < 17; a++) {
    for (int b = 0; b < 17; b++) {
    for (int c = 0; c < 17; c++) {
    for (int d = 0; d < 17; d++) {
      //int u = usage[a][b][c][d];
      int u = getusage(a,b,c,d);
      if (u > max) {
        max = u;
        maxa = a;
        maxb = b;
        maxc = c;
        maxd = d;
      }
    };
    };
    };
    };
    //usage[maxa][maxb][maxc][maxd] = 0;
    store_destroymax(maxa,maxb,maxc,maxd);
    //Memo1->Lines->Add(IntToHex(maxa)+","+IntToHex(maxb)+","+IntToHex(maxc)+","+IntToHex(maxd)+" // "+max);
  }

  //корректируем картинку под нужный набор байтов
  for (int y = 0; y < 192; y++) {
    for (int x = 0; x < 256; x+=4) {
      int minerr = 32768;
      int besti;
      for (int i = 0; i < 256; i++) { //ищем лучший набор байтов
        int err;
        unsigned char* byte2colors_i = byte2colors[i];
        int* net_y = net[y];
        err = abs(byte2colors_i[0] - net_y[x]);
        err+= abs(byte2colors_i[1] - net_y[x+1]);
        err+= abs(byte2colors_i[2] - net_y[x+2]);
        err+= abs(byte2colors_i[3] - net_y[x+3]);
        if (err < minerr) {
          minerr = err;
          besti = i;
        };
      };
      net[y][x]   = byte2colors[besti][0];
      net[y][x+1] = byte2colors[besti][1];
      net[y][x+2] = byte2colors[besti][2];
      net[y][x+3] = byte2colors[besti][3];
      //repic[y*64+(x>>2)] = besti;
      repic[((y&0xc0) + ((y&0x07)<<3) + ((y&0x38)>>3))*64+(x>>2)] = besti;
    };
  };

  for (int dy = 0; dy < 4; dy++) { //генерим смещения чанковой штриховки по y (чтобы получить 4 строчки)
    for (int dx = 0; dx < 4; dx++) { //генерим смещения чанковой штриховки по x (чтобы получить все фазы)
      for (int i = 0; i < 256; i++) {
        unsigned char a = (byte2colors[i][0] > chunkpixelnumber[((0+dx)&0x03) + dy*4])?1:0;
        unsigned char b = (byte2colors[i][1] > chunkpixelnumber[((1+dx)&0x03) + dy*4])?1:0;
        unsigned char c = (byte2colors[i][2] > chunkpixelnumber[((2+dx)&0x03) + dy*4])?1:0;
        unsigned char d = (byte2colors[i][3] > chunkpixelnumber[((3+dx)&0x03) + dy*4])?1:0;
        rebyte[(dy*4 + dx)*512 + i]     = ((a<<3) + (b<<2) + (c<<1) + d)<<4;
        rebyte[(dy*4 + dx)*512 + i +256] = (a<<3) + (b<<2) + (c<<1) + d;
      };
    };
  };
}

void __fastcall TForm1::FormCreate(TObject *Sender)
{
  AnsiString fn;
  FILE* fout;

  process("layer1.BMP");
  fn = "repic";
  fout = fopen(fn.c_str(), "wb");
  fwrite(repic, sizeof(repic), 1, fout);
  fclose(fout);
  fn = "rebyte";
  fout = fopen(fn.c_str(), "wb");
  fwrite(rebyte, sizeof(rebyte), 1, fout);
  fclose(fout);

  process("layer2.BMP");
  fn = "repic1";
  fout = fopen(fn.c_str(), "wb");
  fwrite(repic, sizeof(repic), 1, fout);
  fclose(fout);
  fn = "rebyte1";
  fout = fopen(fn.c_str(), "wb");
  fwrite(rebyte, sizeof(rebyte), 1, fout);
  fclose(fout);

/*
  fn = "byte2colors";
  //FILE* fout;
  fout = fopen(fn.c_str(), "wb");
  for (int i = 0; i < 256; i++) {
    fwrite(&(byte2colors[i][0]), 4, 1, fout);
  };
  fclose(fout);
*/

/*  for (int i = 0; i < 16; i++) {
    drawframe();
  };*/
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  drawframe();
}
//---------------------------------------------------------------------------

