{
   This program 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; either
  version 2 of the License, or (at your option) any later version.

  This program 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 this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
}
unit Umain_hlp;

interface
uses windows, classes, messages, sysutils, dialogs, graphics,
 GR32_Image, jpeg, gr32, GR32_Transforms, FreeBitmap, FreeImage, skinzip, GR32_Resamplers;

const
  app_title = 'nMosaic(엔모자이크) - 이미지,기념사진,디카사진을 개성있는 모자이크로';
  home_url = 'http://iblogbox.com/nview';
  wm_base = wm_user+1000;
  wm_progress = wm_base+1;
  wm_message = wm_base+2;
  wm_message_end = wm_base+3;
  wm_error = wm_base+4;
  wm_mosaic_proc1 = wm_base+11;
  wm_mosaic_proc2 = wm_base+12;
  wm_mosaic_proc3 = wm_base+13;
  CRLF = #13#10;

type
  Pimagedata = ^Timagedata;
  Timagedata = record
    name:string;
    bitmap:tbitmap32;
    r,g,b:integer;
    diff:integer;
    usecount:integer;
    used:boolean;
  end;

  Tloadimage = class(TThread)
  private
    procedure proc_image(dir,name:string);
  protected
    procedure Execute; override;
  public
    formhandle:thandle;
    thumbsize:integer;
    selectfolder:string;
    convertmosaic:boolean;

    constructor CreateThread;
    destructor Destroy; override;
  end;

  Tmosaic = class(TThread)
  private
    mosaic: array of array of TColor32;
    procedure make_mosaicdata;
  protected
    procedure Execute; override;
  public
    formhandle:thandle;
    srcbmp:TBitmap32;
    errmsg:String;

    constructor CreateThread;
    destructor Destroy; override;
  end;

  Tmakewebviewer = class(TThread)
  private
    description:string;
    function maketile(bmp:tbitmap32):integer;
    procedure makehtml;
  protected
    procedure Execute; override;
  public
    formhandle:thandle;
    srcbitmap:tbitmap32;
    srcfilename,destfolder,title,points:string;
    level:integer;
    htmlurl:string;
    thumbsize:integer;
    skiptile:boolean;
    errmsg:string;

    constructor CreateThread;
    destructor Destroy; override;
  end;

  procedure clear_imagelist;
  function getnearestiamgedata(r,g,b:integer):Pimagedata;

var
  imagelist:tlist;
  loadimage:Tloadimage;
  isloadimage:boolean;
  lastloadir:string;
  mosaicwork:Tmosaic;
  calc_mosaicsize:integer;
  makewebviewer:Tmakewebviewer;

implementation
uses Ufunction, Uconfig;

procedure Resample(Src,Dst: TBitmap32; X,Y: Integer; Filter:TStretchFilter);
var
  RectS: TRect;
  RectD: TRect;
  DstClipW,DstClipH:Trect;
  resampler:TCustomResampler;
Begin
  //(NearestFilter, LinearFilter,SplineFilter, LanczosFilter, MitchellFilter);
  RectS.Top := 0;
  RectS.Left := 0;
  RectS.Right := src.Width;
  RectS.Bottom := src.Height;
  RectD.Top := 0;
  RectD.Left := 0;
  RectD.Right := X;
  RectD.Bottom := Y;
  //DstClipW := DstClip.Right - DstClip.Left;
  //DstClipH := DstClip.Bottom - DstClip.Top;
  Dst.Clear(clGray32);
  Dst.Width:=X;
  Dst.Height:=Y;
  Src.DrawTo(dst,RectD,RectS);

  case Filter of
    sfNearest:resampler:=TNearestResampler.Create;
    sfDraft:resampler:=TDraftResampler.Create;
    sfLinear:resampler:=TLinearResampler.Create;
  else
    resampler:=TKernelResampler.Create;
    with resampler as TKernelResampler do
      case Filter of
        sfCosine: Kernel := TCosineKernel.Create;
        sfSpline: Kernel := TSplineKernel.Create;
        sfLanczos: Kernel := TLanczosKernel.Create;
        sfMitchell: Kernel := TMitchellKernel.Create;
      end;
  end;
  try
    StretchTransfer(Dst, RectD, RectD, Src, RectS, resampler, dmCustom, nil);
  finally
    resampler.Free;
  end;
end;

procedure clear_imagelist;
var
  i:integer;
  data:Pimagedata;
begin
  for i:=0 to imagelist.Count-1 do begin
    data:=Pimagedata(imagelist.Items[i]);
    freeandnil(data.bitmap);
    dispose(data);
  end;
  imagelist.Clear;
end;

function imagelist_sort(Item1, Item2: Pointer): Integer;
var
  data1,data2:Pimagedata;
begin
  data1:=Pimagedata(item1);
  data2:=Pimagedata(item2);
  if data1.diff>data2.diff then
    result:=1
  else if data1.diff<data2.diff then
    result:=-1
  else
    result:=0;
end;

function getnearestiamgedata(r,g,b:integer):Pimagedata;
var
  i:integer;
  data:Pimagedata;
  m1,m2:integer;
  bmp:tbitmap32;
begin
  m2:=9999999;
  for i:=0 to imagelist.Count-1 do begin
    data:=Pimagedata(imagelist.Items[i]);
    m1:=abs(data.r-r)+abs(data.g-g)+abs(data.b-b);
    if (data.usecount<2) and (m2>m1) then begin
      m2:=m1;
      result:=data;
    end;
//    if data.diff<20 then break;
  end;

  result.usecount:=result.usecount+1;
  if result.usecount>=2 then result.usecount:=0;
  result.used:=true;

  if result.bitmap<>nil then exit;

  result.bitmap:=tbitmap32.Create;
  if sysutils.FileExists(lastloadir+result.name)=false then exit;

  result.bitmap.LoadFromFile(lastloadir+result.name);
  bmp:=tbitmap32.Create;
  try
    if (config.c_mosaicresize<0) or (config.c_mosaicresize>4) then
      config.c_mosaicresize:=3;
    Resample(result.bitmap, bmp, config.c_mosaicimgsize, config.c_mosaicimgsize, TStretchFilter(config.c_mosaicresize));
    result.bitmap.Width:=bmp.Width;
    result.bitmap.Height:=bmp.Height;
    result.bitmap.Draw(0,0,bmp);
  finally
    bmp.Free;
  end;

//  imagelist.Sort(imagelist_sort);
//  result:=Pimagedata(imagelist.Items[0]);
end;

{Tmosaic}
constructor Tmosaic.CreateThread;
begin
  inherited Create(true);
  FreeOnTerminate:=false;
  srcbmp:=tbitmap32.Create;
end;

destructor Tmosaic.Destroy;
begin
  srcbmp.Free;
  inherited Destroy;
end;

procedure Tmosaic.Execute;
var
  i,j:integer;
  w,h:integer;
  bmp1:tbitmap32;

  r,g,b:byte;
  c2,m2,y2,k2:byte;
  H1, S1, L1: Byte;
  data:Pimagedata;
  pt:tpoint;
  tot,k:integer;
begin
make_mosaicdata;
//bmp1:=tbitmap32.Create;
try
 try
  for i:=0 to imagelist.Count-1 do
    Pimagedata(imagelist.Items[i]).used:=false;

  w:=high(mosaic[0])*config.c_mosaicimgsize;
  h:=high(mosaic)*config.c_mosaicimgsize;

//  bmp1.Width:=w;
//  bmp1.Height:=h;
//  bmp1.Clear(clwhite32);

  sendmessage(formhandle, wm_mosaic_proc1, w, h);

  k:=0;
  for i:=0 to high(mosaic) do begin
   for j:=0 to high(mosaic[i]) do begin
      b:=(mosaic[i][j] and $FF);
      g:=(mosaic[i][j] shr 8) and $FF;
      r:=(mosaic[i][j] shr 16) and $FF;
      data:=getnearestiamgedata(r,g,b);
//      bmp1.Draw(j*config.c_mosaicimgsize,i*config.c_mosaicimgsize,data.bitmap);

      pt.X:=j*config.c_mosaicimgsize;
      pt.Y:=i*config.c_mosaicimgsize;
      sendmessage(formhandle, wm_mosaic_proc2, integer(@pt), integer(data.bitmap));

      inc(k);
      if random(3)=0 then begin
        tot:=(high(mosaic)+1)*(high(mosaic[i])+1);
        sendmessage(formhandle, wm_progress, tot, k);
        sleep(0);
      end;

      if self.Terminated then break;
   end;
   if self.Terminated then break;
  end;

//  sendmessage(formhandle, wm_mosaic_proc3, integer(bmp1), 0);
  if self.Terminated=false then
    sendmessage(formhandle, wm_progress, tot, tot);
 except
   on E: Exception do errmsg:=E.Message;
 end;
//  image1.Bitmap.Assign(bmp1);
finally
//  bmp1.Free;
end;
end;

procedure Tmosaic.make_mosaicdata;
var
   x,y,i,j,x1,y1:integer;
   p1:Pcolor32array;
   r,g,b:byte;
   SS:Pcolor32;

   Bm:TBitmap32;
   size:Integer;
begin
  bm:=srcbmp;
  size:=calc_mosaicsize;

  SS := @bm.Bits[0];

  y:=0;
  y1:=0;
  repeat
    p1:=bm.scanline[y];
    setlength(mosaic,y1+1);
    repeat
      j:=1;
      repeat
      x:=0;
      x1:=0;
      repeat
        setlength(mosaic[y1],x1+1);
        mosaic[y1][x1]:=p1[x];
        inc(x1);

        b:=(p1[x] and $FF);
        g:=(p1[x] shr 8) and $FF;
        r:=(p1[x] shr 16) and $FF;
        i:=1;
       repeat
    SS^ := $FF000000 + r shl 16 + g shl 8 + b;
    inc(SS);
       inc(x);
       inc(i);
       until (x>=bm.width) or (i>size);
      until x>=bm.width;
      inc(j);
      inc(y);
      until (y>=bm.height) or (j>size);
    until (y>=bm.height) or (x>=bm.width);

    inc(y1);
  until y>=bm.height;

end;

{Tloadimage}
constructor Tloadimage.CreateThread;
begin
  inherited Create(true);
  FreeOnTerminate:=false;
  thumbsize:=0;
end;

destructor Tloadimage.Destroy;
begin
  inherited Destroy;
end;

procedure Tloadimage.Execute;
var
  SR:TSearchRec;
  a,s1:string;
  tot,k:integer;
  i:integer;
  data:Pimagedata;
  strlist:tstringlist;
  s:string;
begin
  s1:=selectfolder;
  if s1[length(s1)]<>'\' then s1:=s1+'\';

  tot:=0;
  if FindFirst(s1+'*.*', faAnyFile, SR) = 0 then
   repeat
     if self.Terminated then break;
     if (SR.Attr <> faDirectory) and (SR.Name[1] <> '.') then begin
       a:=lowercase(sysutils.ExtractFileExt(SR.Name));
       if (a='.jpg') then
         inc(tot);
     end;
   Until (FindNext(SR)<>0);
  FindClose(SR);

  k:=0;
  if FindFirst(s1+'*.*', faAnyFile, SR) = 0 then
   repeat
     if self.Terminated then break;
     if (SR.Attr <> faDirectory) and (SR.Name[1] <> '.') then begin
       a:=lowercase(sysutils.ExtractFileExt(SR.Name));
       if (a='.jpg') then begin
         proc_image(s1,sr.Name);
         inc(k);
         if random(3)=0 then begin
           sendmessage(formhandle, wm_progress, tot, k);
           sleep(0);
         end;
       end;
     end;
   Until (FindNext(SR)<>0);
  FindClose(SR);

  strlist:=tstringlist.Create;
  try
    for i:=0 to imagelist.Count-1 do begin
      data:=Pimagedata(imagelist.Items[i]);
      s:=data.name+#9+format('%d,%d,%d',[data.r,data.g,data.b]);
      strlist.Add(s);
    end;
    strlist.SaveToFile(s1+'color.ini');
  finally
    strlist.Free;
  end;

  isloadimage:=self.Terminated=false;
  if isloadimage then
    sendmessage(formhandle, wm_progress, tot, tot);
end;

procedure Tloadimage.proc_image(dir,name:string);
var
  data:Pimagedata;
  x,y:integer;
  p1:Pcolor32array;
  avgr,avgg,avgb:integer;
  bmp:tbitmap32;
  s:string;
begin
new(data);
data.name:=name;
data.bitmap:=tbitmap32.Create;
try
  data.bitmap.LoadFromFile(dir+name);

  avgr:=0;
  avgg:=0;
  avgb:=0;
  for y:=0 to data.bitmap.Height do begin
    p1:=data.bitmap.scanline[y];
    try
      for x:=0 to data.bitmap.Width do begin
        avgb:=avgb+(p1[x] and $FF);
        avgg:=avgg+(p1[x] shr 8) and $FF;
        avgr:=avgr+(p1[x] shr 16) and $FF;
      end;
    except
    end;
  end;

  data.r:=avgr div (data.bitmap.Width*data.bitmap.Height);
  data.g:=avgg div (data.bitmap.Width*data.bitmap.Height);
  data.b:=avgb div (data.bitmap.Width*data.bitmap.Height);
  data.usecount:=0;

  imagelist.Add(data);

  if thumbsize=0 then exit;

  bmp:=tbitmap32.Create;
  try
    if (config.c_mosaicresize<0) or (config.c_mosaicresize>4) then
      config.c_mosaicresize:=3;
    Resample(data.bitmap, bmp, thumbsize, thumbsize, TStretchFilter(config.c_mosaicresize));
    data.bitmap.Width:=bmp.Width;
    data.bitmap.Height:=bmp.Height;
    data.bitmap.Draw(0,0,bmp);
  finally
    bmp.Free;
  end;

except
  data.bitmap.Free;
  dispose(data)
end;
end;

{Tmakewebviewer}
constructor Tmakewebviewer.CreateThread;
begin
  inherited Create(true);
  FreeOnTerminate:=false;
  srcbitmap:=nil;
end;

destructor Tmakewebviewer.Destroy;
begin
  inherited Destroy;
end;

procedure Tmakewebviewer.Execute;
var
  bmp,bmp2:tbitmap32;
  i:integer;
  s:string;
  tw,th:integer;
  bmp3:tbitmap;
  jpg:tjpegimage;
begin
  if srcbitmap=nil then
    bmp:=tbitmap32.Create;
  try
   try
    if srcbitmap=nil then
      bmp.LoadFromFile(srcfilename)
    else
      bmp:=srcbitmap;
    if destfolder[length(destfolder)]<>'\' then destfolder:=destfolder+'\';
    sysutils.ForceDirectories(destfolder+title+'\tile');

    sendmessage(formhandle, wm_message, integer(pchar('조각이미지를 생성중입니다.')), 0);
    description:=format('원본사이즈 %d X %d (픽셀)',[bmp.Width, bmp.Height]);
    maketile(bmp);

    if self.Terminated then exit;

    sendmessage(formhandle, wm_message, integer(pchar('미리보기이미지를 생성중입니다.')), 0);

    bmp2:=tbitmap32.Create;
    bmp3:=tbitmap.Create;
    jpg:=tjpegimage.Create;
    try
      if bmp.Width>bmp.Height then begin
        tw:=thumbsize;
        th:=(bmp.Height*tw) div bmp.Width;
      end else begin
        th:=thumbsize;
        tw:=(bmp.Width*th) div bmp.Height;
      end;
      Resample(bmp, bmp2, tw, th, sfLanczos);
      bmp2.AssignTo(bmp3);
      jpg.Assign(bmp3);
      jpg.SaveToFile(destfolder+title+'\preview.jpg');
    finally
      bmp2.Free;
      bmp3.Free;
      jpg.Free;
    end;

    makehtml;
   except
     on E: Exception do errmsg:=E.Message;
   end;
  finally
    if srcbitmap=nil then
      bmp.Free;
  end;
end;

procedure Tmakewebviewer.makehtml;
var
  strlist:tstringlist;
  s,s1:string;
  unZip: TSkinZip;
begin
  try
   UnZip:=TSkinZip.Create(nil);
   UnZip.UzWithoutPath:=false;
   UnZip.UnZipfile:=GetAppDirectory+'template\webviewer.zip';
   if UnZip.UnzipAllTo(destfolder+title) then
   else begin
     sendmessage(formhandle, wm_message, integer(pchar('템플릿파일 압축해제에 실패 했습니다.')), 0);
     exit;
   end;
  finally
   UnZip.Free;
  end;

  s:=destfolder+title+'\index.html';
  strlist:=tstringlist.Create;
  try
    if sysutils.FileExists(s)=false then exit;
    strlist.LoadFromFile(s);
    s1:=strlist.Text;
    s1:=sysutils.StringReplace(s1,'{points}',format('points[%d]=new Array(%s);',[level,points]),[rfReplaceAll]);
    s1:=sysutils.StringReplace(s1,'{level}',inttostr(level),[rfReplaceAll]);
    s1:=sysutils.StringReplace(s1,'{title}',title,[rfReplaceAll]);
    s1:=sysutils.StringReplace(s1,'{date}',datetimetostr(now),[rfReplaceAll]);
    s1:=sysutils.StringReplace(s1,'{version}',GetVersion(paramstr(0)),[rfReplaceAll]);
    s1:=sysutils.StringReplace(s1,'{description}',description,[rfReplaceAll]);

    strlist.Text:=s1;
    strlist.SaveToFile(s);
    htmlurl:=s;
    sendmessage(formhandle, wm_message, integer(pchar('모두 완료했습니다.')), 0);
  finally
    strlist.Free;
  end;
end;

procedure savetojpeg(bmp1:tbitmap32;filename:string);
var
  FBitmap:TFreeWinBitmap;
  stream:TMemorystream;
  FreeMemoryIO1:TFreeMemoryIO;
begin
  FBitmap:=TFreeWinBitmap.Create;
  stream:=TMemorystream.Create;
  bmp1.SaveToStream(stream);
  FreeMemoryIO1:=TFreeMemoryIO.Create(stream.Memory,stream.Size);
  FBitmap.LoadFromMemory(FreeMemoryIO1);
  FBitmap.ConvertTo24Bits;
  FreeMemoryIO1.Free;
  stream.Free;
  FBitmap.Save(filename,FIF_JPEG,JPEG_QUALITYGOOD);
  FBitmap.Free;
end;

function Tmakewebviewer.maketile(bmp:tbitmap32):integer;
const
  blocksize=256;

var
  i,j,k,tot,size:integer;
  bmp1,bmp2:tbitmap32;
  tl,tt,tw,th:integer;
  s:string;
  dest,src:trect;
  bmp3:tbitmap;
  jpg:tjpegimage;
begin
{
  if level=0 then begin
    size:=blocksize;
    tot:=1;
  end else begin
    j:=1;
    for i:=1 to level do j:=j*2;
    tot:=j;
    size:=blocksize*tot;
  end;
}
  if (bmp.Width>bmp.Height) then
    k:=bmp.Width
  else
    k:=bmp.Height;
  j:=1;
  for i:=1 to 5 do begin
    j:=j*2;
    if blocksize*j>k then break;
  end;
  level:=i;
  tot:=j;
  size:=blocksize*tot;

  bmp1:=tbitmap32.Create;
  bmp2:=tbitmap32.Create;
  jpg:=Tjpegimage.Create;
  bmp3:=tbitmap.Create;
  try
    bmp1.Width:=size;
    bmp1.Height:=size;
    bmp1.Clear(clwhite32);

    if (bmp.Width<size) and (bmp.Height<size) then begin
      bmp2.Assign(bmp);
      result:=1;
    end else begin
      if bmp.Width>bmp.Height then begin
        tw:=size-round(size*0.1);
        th:=(bmp.Height*tw) div bmp.Width;
      end else begin
        th:=size-round(size*0.1);
        tw:=(bmp.Width*th) div bmp.Height;
      end;
      Resample(bmp, bmp2, tw, th, sfNearest);
      result:=0;
    end;

    tl:=(bmp1.Width div 2)-(bmp2.Width div 2);
    tt:=(bmp1.Height div 2)-(bmp2.Height div 2);
    points:=format('%d,%d,%d,%d',[tl,tt,bmp2.Width,bmp2.Height]);
    if skiptile then exit;

    bmp1.Draw(tl,tt,bmp2);

    bmp2.Width:=blocksize;
    bmp2.Height:=blocksize;
    dest:=rect(0,0,bmp2.Width,bmp2.Height);
    k:=0;
    for i:=0 to tot-1 do begin
      for j:=0 to tot-1 do begin
        if self.Terminated then exit;
        src.Left:=i*blocksize;
        src.Top:=j*blocksize;
        src.Right:=src.Left+blocksize;
        src.Bottom:=src.Top+blocksize;
        bmp2.Canvas.CopyRect(dest,bmp1.Canvas,src);
        s:=destfolder+title+'\tile\'+format('tile-%d-%d-%d.jpg',[level,i,j]);

        bmp2.AssignTo(bmp3);
        jpg.Assign(bmp3);
        jpg.SaveToFile(s);
//        bmp2.SaveToFile(s);
//        savetojpeg(bmp2,s);
        inc(k);
        sendmessage(formhandle, wm_progress, tot*tot, k);
      end;
    end;
  finally
    bmp2.Free;
    bmp1.Free;
    jpg.Free;
    bmp3.Free;
  end;
end;

initialization
  imagelist:=tlist.Create;
  isloadimage:=false;

finalization

end.
