/******************************************************************************
 * program:     rasimg library 0.23                                           *
 * function:    Object set for handling vector images.                        *
 * modul:       vec_image.cc                                                  *
 * licency:     GPL or LGPL                                                   *
 * Copyright: (c) 2018-2023 Jaroslav Fojtik                                   *
 ******************************************************************************/
#include <stdlib.h>
#include <stdio.h>

#define _USE_MATH_DEFINES
#include <math.h>

#include "vecimage.h"

inline float RAD2DEG(const float x) {return((x)*180.0f/(float)M_PI);}



VectorRaster::VectorRaster(Raster2DAbstract *iniRaster, float iniCenterX, float iniCenterY, float TrDx, float TrDy, float alpha)
{
float CosAlpha = cos(alpha);
float SinAlpha = sin(alpha);

  Raster = iniRaster;
  if(Raster!=NULL) Raster->UsageCount++;
  Palette = NULL;

  CenterX = iniCenterX;
  CenterY = iniCenterY;

  float Divider = 2*CosAlpha*CosAlpha - 1;
  TrDx = fabs(TrDx / 2);
  float dx2 = TrDx;
  TrDy = fabs(TrDy / 2);
  float dy2 = TrDy;

  if(fabs(Divider) < 1e-3)
  {
    if(Raster!=NULL && Raster->Size1D>0 && Raster->Size2D>0)
    {
      const float k = Raster->Size1D / (float)Raster->Size2D;
      dx2 = sqrt(0.5) / (1+k) * TrDx;
      dy2 = k * sqrt(0.5) / (1+k) * TrDx;
    }
  }
  else
  {
    dx2 = fabs((TrDx*fabs(CosAlpha)-fabs(SinAlpha)*TrDy) / Divider);
    dy2 = fabs((-TrDx*fabs(SinAlpha)+TrDy*fabs(CosAlpha)) / Divider);
  }

  RightCenterX = iniCenterX + dx2*CosAlpha;
  RightCenterY = iniCenterY + dx2*SinAlpha;

  TopCenterX = iniCenterX + dy2*SinAlpha;
  TopCenterY = iniCenterY + dy2*CosAlpha;
}


VectorRaster::VectorRaster(float iniCenterX, float iniCenterY, float dx, float dy, float alpha)
{
  Raster = NULL;
  Palette = NULL;

  CenterX = iniCenterX;
  CenterY = iniCenterY;

  dx /= 2;
  dy /= 2;

  RightCenterX = iniCenterX + dx*cos(alpha);
  RightCenterY = iniCenterY + dx*sin(alpha);

  TopCenterX = iniCenterX + dy*sin(alpha);
  TopCenterY = iniCenterY + dy*cos(alpha);
}


VectorRaster::VectorRaster(float iniBottomRect, float iniTopRect, float iniRightRect, float iniLeftRect)
{
  Raster = NULL;
  Palette = NULL;
  TopCenterX = CenterX = (iniRightRect + iniLeftRect) / 2;
  RightCenterY = CenterY = (iniBottomRect + iniTopRect) / 2;
  RightCenterX = iniRightRect;
  TopCenterY = iniTopRect;
}


VectorRaster::VectorRaster(const VectorRaster& VR)
{
  CenterX = VR.CenterX;
  CenterY = VR.CenterY;
  RightCenterX = VR.RightCenterX;
  RightCenterY = VR.RightCenterY;
  TopCenterX = VR.TopCenterX;
  TopCenterY = VR.TopCenterY;
  AttachRaster(VR.Raster);
  AttachPalette(VR.Palette);
}


VectorRaster::~VectorRaster()
{
  AttachRaster(NULL);
  AttachPalette(NULL);
}


VectorObject *VectorRaster::Duplicate(void) const
{
  return new VectorRaster(*this);
}


void VectorRaster::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 VectorRaster::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++;
   }
}


void VectorRaster::FeaturesEPS(uint32_t & Feature) const
{
int ColorMode = ImageType();
  if(ColorMode==ImagePalette)
    if(Palette->GetPlanes()<=8*Palette->Channels())
	Feature|=EPS_rlecmapimage;
  //Feature|=EPS_colorimage;  
}


void VectorRaster::Transform(const AbstractTransformXY &Tx)
{
  Tx.ApplyTransform(RightCenterX, RightCenterY);
  Tx.ApplyTransform(TopCenterX, TopCenterY);
  Tx.ApplyTransform(CenterX, CenterY);
}


/** 0-none (ImageNone), 1-gray (ImageGray), 2-palette (ImagePalette), 3-true color (ImageTrueColor). */
IMAGE_TYPE VectorRaster::ImageType(void) const
{
  if(Raster==NULL) return ImageNone;
  if(Raster->Size1D==0 || Raster->Size2D==0) 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;
}


temp_string VectorRaster::Export2EPS(PS_State *PSS) const
{
  return temp_string();
}

void DumpRaster2File(FILE *f, Raster2DAbstract *Raster, APalette *Palette,
		float CenterX, float CenterY,
		float dx, float dy, float RotAngle, int ColorMode)
{
unsigned X, Y;
RGBQuad RGB;
char RasterPlanes;

  if(f==NULL || Raster==NULL ||
     Raster->Size1D<=0 || Raster->Size2D<=0) return;

  RasterPlanes = labs(Raster->GetPlanes());
  if(ColorMode==ImagePalette)
    if(Palette->GetPlanes()<=8*Palette->Channels())
	ColorMode=12;

  if(ColorMode==12)
  {
    fprintf(f,
	"%% define the colormap\n"
	"/cmap %u string def\n"
	"%% load up the colormap\n"
	"currentfile cmap readhexstring", 3*Palette->GetSize1D());	//3x size of a palette
    for(X=0; X<Palette->GetSize1D(); X++)
	{
	if(X % 16 == 0) fputc('\n',f);
	Palette->Get(X,&RGB);
	fprintf(f,"%.2X%.2X%.2X ",
	     (int)RGB.R, (int)RGB.G, (int)RGB.B);
	}
    fputs("\npop pop  % lose return values from readhexstring\n", f);
  }

  fprintf(f,"\n"
      "%% define string to hold a scanline's worth of data\n"
      "/pix %d string def\n"
      "\n"
      "%% define space for color conversions\n"
      "/grays %u string def  %% space for gray scale line\n"
      "/npixls 0 def\n"
      "/rgbindx 0 def\n",
          (ColorMode==ImagePalette || ColorMode==12)
	    ? Raster->GetSize1D()
	    : (Raster->GetSize1D()*RasterPlanes+7)/8,
          Raster->GetSize1D());

   fputs("\ngsave", f);

   if(fabs(RotAngle) > 1e-5)
   {
     fprintf(f,"\n%2.2f %2.2f translate\n%g rotate\n%2.2f %2.2f translate",
     CenterX,CenterY, RotAngle, -CenterX,-CenterY);
   }
   
   {
     float XStart = CenterX;
     float YStart = CenterY;
     XStart -= dx;
     YStart -= dy;

     if(fabs(XStart)>1e-3 || fabs(YStart)>1e-3)
       {
       fputs("\n% lower left corner\n", f);
       fprintf(f,"%2.2f %2.2f translate\n", XStart, YStart);	// shift y in negative direction
       }
   }

   fprintf(f,"\n"
       "%% size of image (on paper, in 1/72inch coords)\n"
       "%2.2f %2.2f scale\n\n", 2*dx, 2*dy);

   if((ColorMode==ImagePalette || ColorMode==12)) Y=8;
   else
   {
     Y = RasterPlanes/Raster->Channels();
     if(Y>=16) Y=8;
   }
   fprintf(f,
     "%% dimensions of data\n"
     "%u %u %d\n"					//%SizeX %SizeY %Planes
     "%% mapping matrix\n"
     "[ %u 0 0 -%u 0 %u]\n\n",				//%SizeX %SizeY %SizeY
    Raster->Size1D, Raster->Size2D, Y,
    Raster->Size1D, Raster->Size2D, Raster->Size2D);

   if(ColorMode==12)
     {
     fputs("rlecmapimage\n", f);
     }
   else
     {
     fprintf(f,
       "{currentfile pix readhexstring pop}\n"
       "%simage\n",
      (ColorMode>ImageGray)?"false 3 color":"" );
     }

   unsigned BytesPerLine = (((unsigned long)RasterPlanes)*Raster->GetSize1D()+7) / 8;

     // When negative size is given, flip raster data
   if(dy < 0) Flip2D(Raster);
   if(dx < 0) Flip1D(Raster);

   for(Y=0; Y<Raster->Size2D; Y++)
     {
     switch(ColorMode)
	{
	case 12:		//palette images - less than 256 colors, RLE compressed.
	  {
	  Raster1DAbstract *RasLine = Raster->GetRowRaster(Y);
	  int CharsOnLine = 0;
	  for(X=0; X<RasLine->GetSize1D(); X++)
	    {
	    if(X%128 == 0)
	      {
	      if(CharsOnLine >= 254-2)
	        {
	        fputc('\n',f);		//Lines in DSC documents must be shorter than 255 characters.
	        CharsOnLine = 0;
	        }
	      CharsOnLine += 2;
	      if(RasLine->GetSize1D()-X > 128)
		fprintf(f,"%.2X", (128-1)|0x80);
	      else
		fprintf(f,"%.2X", (unsigned)((RasLine->GetSize1D()-X-1)|0x80));
	      }
	      if(CharsOnLine >= 254-2)
	      {
	      fputc('\n',f);		//Lines in DSC documents must be shorter than 255 characters.
	      CharsOnLine = 0;
	      }
	    CharsOnLine += 2;
	    fprintf(f,"%.2X", RasLine->GetValue1D(X));
	    }
	  break;
	  }
    case ImagePalette:
	  {
	  if(RasterPlanes==8)  //speedup trick for 8 planes
	    {
	    uint8_t *ptrb = (uint8_t *)Raster->GetRow(Y);
	    for(X=0; X<BytesPerLine; X++)
		{
		Palette->Get(*ptrb++,&RGB);
		fprintf(f,"%.2X%.2X%.2X",
		     (int)RGB.R, (int)RGB.G, (int)RGB.B);
		}
	     }
	  else
	     {
	     Raster1DAbstract *RasLine = Raster->GetRowRaster(Y);
	     if(RasLine)
	       for(X=0; X<RasLine->GetSize1D(); X++)
		  {
		  uint8_t b = RasLine->GetValue1D(X);
		  Palette->Get(b,&RGB);
		  fprintf(f,"%.2X%.2X%.2X",
		     (int)RGB.R, (int)RGB.G, (int)RGB.B);
		  }
	      }
	break;
	}

   case ImageTrueColor:
	   if(RasterPlanes==24) goto FAST_COPY;
	   {
	     Raster1DAbstract *RasLine = Raster->GetRowRaster(Y);
	     if(RasLine)
	       {
	       uint16_t chars_on_line = 0;
	       for(X=0; X<RasLine->GetSize1D(); X++)
		 {
	         const uint32_t w = RasLine->GetValue1D(X);
		 chars_on_line += 6;
		 if(chars_on_line>253)
		 {
		   fputs("\r\n",f);
		   chars_on_line = 6;
		 }
	         fprintf(f,"%.2X%.2X%.2X", w&0xFF, (w>>8)&0xFF, (w>>16)&0xFF);
	         }
               }
           }
	   break;

   default:
     {
	if(RasterPlanes==8 ||
	   RasterPlanes==4 ||
	   RasterPlanes==1)	//speedup trick for 8, 4 and 1 planes
	 {
FAST_COPY:uint8_t *ptrb = (uint8_t *)Raster->GetRow(Y);
	 for(X=0; X<BytesPerLine; X++)
	    {
	    fprintf(f,"%.2X",(unsigned)*ptrb++);
          }
        }
	else	/* Expand other bit depths to 8 bits */
	{
	 Raster1DAbstract *RasLine = Raster->GetRowRaster(Y);	  
	 if(RasLine)
	   {
	   int k = RasterPlanes - 8;
	   uint32_t w;
	   for(X=0;X<RasLine->GetSize1D();X++)
	   {
	     w = RasLine->GetValue1D(X);
	     if(k>0) w>>=k;
	     fprintf(f,"%.2X", w);
	   }
         }
       }
     }
   }

   fputc('\n',f);
  //    if AlineProc<>nil then AlineProc^.NextLine;
 }  // for(y....

 fputs("\ngrestore\n",f);

	// Return original flip
 if(dy < 0) Flip2D(Raster);
 if(dx < 0) Flip1D(Raster);
}


void VectorRaster::CalcBoundingBox(FloatBBox &bbx) const
{
  UpdateBBox(bbx, 0, CenterX+(RightCenterX-CenterX), CenterY+(TopCenterY-CenterY), 0, 0); // Upper right corner
  UpdateBBox(bbx, 0, CenterX-(RightCenterX-CenterX), CenterY-(TopCenterY-CenterY), 0, 0); // Lower left corner
}


void VectorRaster::Export2EPS(FILE *f, PS_State *PSS) const
{
float RotAngle;

  if(fabs(RightCenterX-CenterX)<1e-6 && fabs(RightCenterY-CenterY)<1e-6)
      RotAngle = 0;
  else
      RotAngle = RAD2DEG(atan2(RightCenterY-CenterY, RightCenterX-CenterX));

  DumpRaster2File(f, Raster, Palette, CenterX, CenterY,
		sqrt((RightCenterY-CenterY)*(RightCenterY-CenterY) + (RightCenterX-CenterX)*(RightCenterX-CenterX)),
		sqrt((TopCenterY-CenterY)*(TopCenterY-CenterY) + (TopCenterX-CenterX)*(TopCenterX-CenterX)),
		RotAngle,
		ImageType());
return;
}


#ifdef _DEBUG
void VectorRaster::Dump2File(FILE *F) const
{
  fprintf(F,"VectorRaster{CenterX=%f, CenterY=%f, RightCenterX=%2.2f; RightCenterY=%2.2f; TopCenterX=%2.2f; TopCenterY=%2.2f}\n", 
	  CenterX, CenterY, RightCenterX, RightCenterY, TopCenterX, TopCenterY);
}
#endif
