/******************************************************************************
 * program:     rasimg library                                                *
 * function:    Miscellaneous utilities for manipulation with images.         *
 * modul:       rasterut.cc                                                   *
 * licency:     GPL or LGPL                                                   *
 ******************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#include "types.h"
#include "common.h"

#include "raster.h"
#include "ras_prot.h"

#ifdef _MSC_VER  
  #pragma warning(disable: 4244)
#endif


/* ---------- Global class Image --------- */
void Image::Create(unsigned SizeX, unsigned SizeY, int Planes)
{
  if(Raster)
    {
    if(Raster->GetSize1D()==SizeX && Raster->Size2D==SizeY && 
       Raster->GetPlanes()==(Planes & 0xFF) && 
       Raster->UsageCount==1)		// only this object owns raster
		return;

    Erase();
    }

if(Planes & ImageTrueColor)
  {
  if( (Raster=CreateRaster2DRGB(SizeX, SizeY, Planes&0xFF)) != NULL)
	Raster->UsageCount++;
  }
else
  {
  if( (Raster=CreateRaster2D(SizeX,SizeY,Planes&0xFF))!=NULL)
	Raster->UsageCount++;
  }
}


Image::Image(const Image & I)
{
Image *PImg,*CurrImg;
 if((Raster=I.Raster)!=NULL) Raster->UsageCount++;
 if((Palette=I.Palette)!=NULL) Palette->UsageCount++;
 x=I.x;y=I.y;dx=I.dx;dy=I.dy;RotAngle=I.RotAngle;
 Next=NULL;
 CurrImg=I.Next;
 Comment=NULL;
 if(I.Comment!=NULL) Comment=strdup(I.Comment);
 PImg=this;
 while(CurrImg!=NULL)	//Multiple images
	{
	PImg->Next=new Image;
	if(PImg->Next==NULL) return;	/*Allocation error*/
	PImg=PImg->Next;
	if((PImg->Raster=CurrImg->Raster)!=NULL) CurrImg->Raster->UsageCount++;
	if((PImg->Palette=CurrImg->Palette)!=NULL) CurrImg->Palette->UsageCount++;
	PImg->x=CurrImg->x;PImg->y=CurrImg->y;
	PImg->dx=CurrImg->dx;PImg->dy=CurrImg->dy;
	PImg->RotAngle=CurrImg->RotAngle;
	if(CurrImg->Comment!=NULL) PImg->Comment=strdup(CurrImg->Comment);
	CurrImg=CurrImg->Next;
	}
}

Raster2DAbstract *Image::operator=(Raster2DAbstract *NewRaster)
{
 x=y=dx=dy=0;
 Comment=NULL;
 if(Raster==NewRaster) return(Raster);		//Identical assignment
 if(Raster!=NULL) Erase();
 if( (Raster=NewRaster)!=NULL ) Raster->UsageCount++;
 return(Raster);
}

Image &Image::operator=(const Image &I)
{
Image *PImg;
const Image *CurrImg;
 Erase();

 PImg=this;
 CurrImg=&I;
 goto CopyImg;

 while(CurrImg!=NULL)	//Multiple images
	{
	PImg->Next=new Image;
	if(PImg->Next==NULL) return(*this);
	PImg=PImg->Next;

CopyImg:if((PImg->Raster=CurrImg->Raster)!=NULL)
		CurrImg->Raster->UsageCount++;
	if((PImg->Palette=CurrImg->Palette)!=NULL)
		CurrImg->Palette->UsageCount++;
	PImg->x=CurrImg->x;PImg->y=CurrImg->y;
	PImg->dx=CurrImg->dx;PImg->dy=CurrImg->dy;
	PImg->RotAngle=CurrImg->RotAngle;
	if(CurrImg->Comment!=NULL) PImg->Comment=strdup(CurrImg->Comment);
	CurrImg=CurrImg->Next;
	}
 return(*this);
}


void Image::Erase(void)
{
Image *I,*OldI;

 I=this;
 while(I!=NULL)
   {
   if(I->Raster!=NULL)
      {
      if(I->Raster->UsageCount--<=1) delete I->Raster;
      I->Raster=NULL;
      }
   if(I->Palette!=NULL)
      {
      if(I->Palette->UsageCount--<=1) delete I->Palette;
      I->Palette=NULL;
      }
   if(I->Comment!=NULL)
      {
      free(I->Comment);
      I->Comment=NULL;
      }
   OldI=I;
   I=I->Next;
   OldI->Next=NULL;
   if(OldI!=this) delete(OldI);
   }
}


/* 0-none, 1-gray, 2-palette, 3-true color */
IMAGE_TYPE Image::ImageType(void) const
{
  if(Raster==NULL) return ImageNone;
  if(Raster->Channels()>=3) return ImageTrueColor;
  if(Palette!=NULL)
    {
    if(GrayPalette(Palette,Raster->GetPlanes())) return ImageGray;
    if(Raster->GetPlanes()==24) return ImageTrueColor;
    return ImagePalette;
    }
  if(Raster->GetPlanes()==24) return ImageTrueColor;
  return ImageGray;
}


DWORD Image::GetPixelRGB(unsigned Offset1D, unsigned Offset2D) const
{
  if(Raster==NULL) return 0;
  switch(ImageType() & 0xFF00)
    {
    case ImageTrueColor:
	     return Raster->GetValue2D(Offset1D,Offset2D);
    case ImagePalette:
	     {
	     DWORD b = Raster->GetValue2D(Offset1D,Offset2D);
	     return Palette->GetValue1D(b);
	     }
    default: {
	     DWORD b=Raster->GetValue2D(Offset1D,Offset2D);
	     switch(Raster->GetPlanes())
	     {
	       case  1: b <<= 7; break;
	       case  2: b <<= 6; break;
	       case  4: b <<= 4; break;
	       case 16: b >>= 8; break;
	       case 24: b >>= 16; break;
	       case 32: b >>= 24; break;
	     }
	     return b | 0x100*b | 0x10000*b;
	     }
    }
  return 0;
}


void Image::AttachRaster(Raster2DAbstract *NewRaster)
{
  if(Raster==NewRaster) return;		// Raster is already attached
  if(Raster!=NULL)			// Detach previously attached raster
   {
   if(Raster->UsageCount--<=1) delete Raster;
   Raster=NULL;
   }
  if(NewRaster!=NULL)			// Attach raster now
   {
   Raster=NewRaster; Raster->UsageCount++;
   }
}


void Image::AttachPalette(APalette *NewPalette)
{
  if(Palette==NewPalette) return;		// Raster is already attached
  if(Palette!=NULL)			// Detach previously attached raster
   {
   if(Palette->UsageCount--<=1) delete Palette;
   Palette = NULL;
   }
  if(NewPalette!=NULL)			// Attach raster now
   {
   Palette=NewPalette; Palette->UsageCount++;
   }
}


/* ---------------------------- */


/*This procedure provides vertical flipping of the raster*/
void Flip2D(Raster2DAbstract *r2D)
{
void *ptr,**P1,**P2;

if(r2D==NULL) return;
if(r2D->Data2D==NULL) return;
P1=r2D->Data2D;
P2=r2D->Data2D+(r2D->Size2D-1);
while(P2>P1)
   {
   ptr=*P1;
   *P1=*P2;
   *P2=ptr;
   P1++; P2--;
   }
}


/*This procedure provides horizontal flipping of the raster*/
void Flip1D(Raster2DAbstract *r2D)
{
WORD *B1,*B2,tmp;
unsigned y;
Raster1D_16Bit Buff;

  if(r2D==NULL) return;
  if(r2D->Data2D==NULL) return;

  Buff.Allocate1D(r2D->GetSize1D());
  if(Buff.Data1D==NULL) return;

  for(y=0;y<r2D->Size2D;y++)
      {
      r2D->GetRowRaster(y)->Get(Buff);

      B1 = (WORD *)Buff.Data1D;
      B2 = B1 + r2D->GetSize1D() - 1;

      while(B1<B2)
	 {
	 tmp=*B1;
	 *B1=*B2;
	 *B2=tmp;
	 B1++; B2--;
	 }
      r2D->GetRowRaster(y)->Set(Buff);
      }
  Buff.Erase();
}


class Palette_8bit: virtual public APalette, virtual public Raster1D_8BitRGB
	{
public:	Palette_8bit(int Indices): Raster1D_8BitRGB(Indices) {};
	Palette_8bit(void): Raster1D_8BitRGB() {};

	virtual void Get(unsigned index, RGBQuad *RGB);
	};

class Palette_16bit: virtual public APalette, virtual public Raster1D_16BitRGB
	{
public:	Palette_16bit(int Indices): Raster1D_16BitRGB(Indices) {};
	Palette_16bit(void) {};

	virtual void Get(unsigned index, RGBQuad *RGB);
	};

void Palette_8bit::Get(unsigned index, RGBQuad *RGB)
{
BYTE *ptrb;

 if(RGB==NULL || Data1D==NULL || Size1D<index) return;
 index *= 3; 
 ptrb = ((BYTE *)Data1D)+index;
 RGB->R = *ptrb++;
 RGB->G = *ptrb++;
 RGB->B = *ptrb;
}


void Palette_16bit::Get(unsigned index, RGBQuad *RGB)
{
BYTE *ptrb;

 if(RGB==NULL || Data1D==NULL || Size1D<index) return;
 index *= 6; 
 ptrb = ((BYTE *)Data1D)+index;
 RGB->R = *ptrb;	ptrb += 2;
 RGB->G = *ptrb;	ptrb += 2;
 RGB->B = *ptrb;
}


void APalette::Get(unsigned index, RGBQuad *RGB)
{
 if(RGB==NULL) return;
 RGB->R=R(index);
 RGB->G=G(index);
 RGB->B=B(index);
}


APalette *BuildPalette(unsigned Indices, char type)
{
APalette *pal=NULL;

 switch(type)
	{
	case 0:
	case 8: pal = new Palette_8bit(Indices);  break;
	case 16:pal = new Palette_16bit(Indices); break;
	}
 if(pal)
   if(pal->GetSize1D()<Indices)
	{
	delete pal;
	return(NULL);
	}

return(pal);
}


/** Is this Gray scale palette? */
int GrayPalette(Raster1DAbstractRGB *Palette, int Planes)
{
unsigned i,j;
unsigned PalItems;

 if(Palette==NULL) return(1);
 if(Palette->Data1D==NULL ||
    Palette->Size1D==0) return(1);	//consider invalid palette to be gray

 PalItems = (1<<Planes);
 if(Planes!=0)
   if(PalItems > Palette->GetSize1D())
	{
	//PalItems=Palette->Size1D/3;
	return(0);	//insufficient palette size, ?? non gray ??
	}

 for(i=0;i<PalItems;i++)
	{
	j=i*255 / (PalItems-1);
	if( (Palette->R(i)!=j) ||
	    (Palette->G(i)!=j) ||
	    (Palette->B(i)!=j) ) return(0);
	}
 return(1);
}


/** Make this palette gray. */
int FillGray(Raster1DAbstractRGB *Palette, int Planes)
{
unsigned i,j;

 if(Palette==NULL) return(1);
 if(Planes!=0)
   if(3*(1<<Planes) != Palette->Size1D) return(0);

 if(Palette->Data1D==NULL) return(0);
 for(i=0;i<Palette->Size1D/3-1;i++)
   {
   j=i*255 / (Palette->Size1D/3-1);
   Palette->R(i,j);
   Palette->G(i,j);
   Palette->B(i,j);
   }
 return(1);
}


//-----------------------------------------------------------------------
int ReducePalette(Image *Img, unsigned maxColors)
{
unsigned x,y;
Raster1DAbstract *RowRas,*RowRas2;
APalette *paletettebuff;
unsigned CurrentColors = 0;
DWORD RGBval, RGBval2;
unsigned i;
Raster2DAbstract *Raster2;

  if(Img==NULL || maxColors<=0) return 0;
  if(Img->Raster==NULL) return 0;
  if(Img->ImageType()!=ImageTrueColor)
    {
    BYTE *Reindex;

    if(Img->Palette==NULL || 
       Img->Raster->GetPlanes()>8 || Img->Raster->GetPlanes()<=4) return 0;

    Reindex = (BYTE *)calloc(256,sizeof(DWORD));
    if(Reindex==NULL) return 0;

	/* Calculate a histogram */
    for(y=Img->Raster->Size2D;y>0;y--)
    {
      RowRas = Img->Raster->GetRowRaster(y-1);
      if(RowRas==NULL) continue;

      for(x=0;x<RowRas->GetSize1D();x++)
        {
        Reindex[RowRas->GetValue1D(x)] = 1;
        }
    }

	/* Create remapping matrix. */
    y = 0;
    for(x=0; x<((unsigned)1<<labs(Img->Raster->GetPlanes())); x++)
      {
      if(Reindex[x])
        {
        Reindex[x] = y++;
        }
      }
    
    if(y<=16)
      {
	/* Remap a raster. */
      Raster2 = CreateRaster2D(Img->Raster->Size1D,Img->Raster->Size2D,4);

      for(y=Img->Raster->Size2D;y>0;y--)   //fix for 32 bit planes
        {
        RowRas = Img->Raster->GetRowRaster(y-1);
        RowRas2 = Raster2->GetRowRaster(y-1);
        if(RowRas==NULL || RowRas2==NULL) continue;

        for(x=0;x<RowRas->GetSize1D();x++)
	  {
          RowRas2->SetValue1D(x, Reindex[RowRas->GetValue1D(x)]);
	  }
        }

      if(--Img->Raster->UsageCount <= 0)
	delete Img->Raster;
      Img->Raster = Raster2;
      Img->Raster->UsageCount++;
      }

	/* Remap a palette. */
    if((paletettebuff=BuildPalette(maxColors,8))!=NULL)
      {
      for(x=0;x<Img->Palette->GetSize1D();x++)
        {
        if(x==0 || Reindex[x])
          {
          paletettebuff->SetValue1D(Reindex[x], Img->Palette->GetValue1D(x));
          }
        }
        
        Img->AttachPalette(paletettebuff);
        paletettebuff = NULL;
      }

    free(Reindex);
    return 0;
    }

	/* True color reduction process. */
  //printf("\n---- Image check starts %d ------", maxColors);

  if((paletettebuff=BuildPalette(maxColors,8))==NULL) return 0;

  for(y=Img->Raster->Size2D;y>0;y--)   //fix for 32 bit planes
    {
    RowRas = Img->Raster->GetRowRaster(y-1);
    if(RowRas==NULL) continue;
    for(x=0;x<RowRas->GetSize1D();x++)
      {
      RGBval = RowRas->GetValue1D(x);

      i=0;
      while(i<CurrentColors)
	{
	RGBval2 = paletettebuff->GetValue1D(i);
	if(RGBval2 == RGBval) goto Found;

	if(RGBval2>RGBval)		// keep palette sorted
	  {
	  while(i<CurrentColors)
	    {
	    paletettebuff->SetValue1D(i,RGBval);
	    RGBval = RGBval2;
	    i++;
	    RGBval2 = paletettebuff->GetValue1D(i);
	    }
	  break;
	  }

	i++;
	}
	// Insert last value and extend palette
      paletettebuff->SetValue1D(CurrentColors++,RGBval);
      if(CurrentColors>=maxColors) goto Finish;
Found: {}
      }
    }

  if(CurrentColors<=256)
    {
    if(CurrentColors<=16)
      Raster2 = CreateRaster2D(Img->Raster->Size1D,Img->Raster->Size2D,4);
    else
      Raster2 = CreateRaster2D(Img->Raster->Size1D,Img->Raster->Size2D,8);
    if(Raster2->Data1D==NULL) goto Finish;

      {
      APalette *paletettebuff2 = BuildPalette(1<<Raster2->GetPlanes());
      if(paletettebuff2==NULL) goto Finish;
      Img->AttachPalette(paletettebuff2);
      }

    for(i=0; i<Img->Palette->GetSize1D(); i++)
      {
	Img->Palette->SetValue1D(i, paletettebuff->GetValue1D(i));
      }

    for(y=Img->Raster->Size2D;y>0;y--)   //fix for 32 bit planes
      {
      RowRas = Img->Raster->GetRowRaster(y-1);
      RowRas2 = Raster2->GetRowRaster(y-1);
      if(RowRas==NULL || RowRas2==NULL) continue;

      for(x=0;x<RowRas->GetSize1D();x++)
	{
	RGBval = RowRas->GetValue1D(x);

	for(i=0; i<CurrentColors; i++)
	  {
	  if(paletettebuff->GetValue1D(i) == RGBval)
	    {
	    RowRas2->SetValue1D(x,i);	// index has been found
	    break;
	    }
	  }
	}
      }

    Img->AttachRaster(Raster2);

    //printf("---- Image reduced %d ------", CurrentColors);
    }

Finish:
  if(paletettebuff) {delete paletettebuff;paletettebuff=NULL;}
  //printf("---- Image check ended<<<");

return CurrentColors;
}
