/******************************************************************************
 * program:     rasimg library 0.28                                           *
 * function:    Miscellaneous utilities for manipulation with images.         *
 * modul:       imgut.cc                                                      *
 * licency:     GPL or LGPL                                                   *
 * Copyright: (c) 1998-2024 Jaroslav Fojtik                                   *
 ******************************************************************************/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#include "../jobs/csext.h"
#include "typedfs.h"
#include "common.h"

#include "image.h"
#include "vecimage.h"
#include "ras_prot.h"

#ifdef _MSC_VER  
  #pragma warning(disable: 4244)
  #pragma warning(disable: 4250)	// Inheriting via dominance, standard C++ feature.
#endif



/* ------------ Properties ---------------*/

PropertyItem::PropertyItem(void)
{
  Data=NULL;
  DataSize=0;
  UsageCount=0;
}


PropertyItem::PropertyItem(const char *NewName): Name(NewName)
{
  Data=NULL;
  DataSize=0;
  UsageCount=0;
}


PropertyItem::PropertyItem(const char *NewName, unsigned IniSize): Name(NewName)
{
  DataSize = 0;
  if(IniSize>0)
  {
    Data = malloc(IniSize);
    if(Data) DataSize=IniSize;
  }
  else
  {
    Data=NULL;
  }
  UsageCount=0;
}


PropertyItem::~PropertyItem()
{
  if(Data)
  {
    free(Data);
    Data=NULL;DataSize=0;
  }
}


PropertyList::~PropertyList()
{
  if(pProperties != NULL)
  {
    for(int i=0; i<PropCount; i++)
    {
      if(pProperties[i] != NULL)
      {
        if(pProperties[i]->UsageCount--<=1) delete pProperties[i];
        pProperties[i] = NULL;
      }
    }
    free(pProperties);
    pProperties = NULL;
  }
}


void PropertyList::AddProp(PropertyItem *NewProp)
{
int i;
  if(NewProp==NULL) return;
	// Method 1: list ie empty.
  if(pProperties == NULL)
  {
    pProperties = (PropertyItem**)malloc(sizeof(PropertyItem*));
    if(pProperties==NULL)
    {	// report memory error
      return;
    }
    pProperties[0] = NewProp;
    PropCount=1;
    NewProp->UsageCount++;
    return;
  }
	// Method 2: list has a free slot.
  for(i=0; i<PropCount; i++)
  {
    if(pProperties[i]==NULL)
    {
      pProperties[i] = NewProp;
      NewProp->UsageCount++;
      return;
    }
  }
	// Method 3: list must be reallocated.
  PropertyItem **NewList = (PropertyItem**)realloc(pProperties, sizeof(PropertyItem*)*(PropCount+1));
  if(NewList==NULL)
  {
    return;
  }
  pProperties = NewList;
  pProperties[PropCount++] = NewProp;
  NewProp->UsageCount++;
}


/// Find property with a given name.
/// @param[in]	FindName	Property to search.
/// @param[in,out] PropIDX	Starting search index.
const PropertyItem *PropertyList::Find(const char *FindName, unsigned &PropIDX) const
{
  if(pProperties==NULL) return NULL;

  while(PropIDX<PropCount)
  {    
    const PropertyItem *pProp = pProperties[PropIDX];
    PropIDX++;
    if(pProp->Data!=NULL && pProp->DataSize>0)
    {
      if(pProp->Name==FindName) return pProp;
    }
  }
return NULL;
}


/* ---------- 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)
	InterlockedIncrement(&Raster->UsageCount);
  }
else
  {
  if( (Raster=CreateRaster2D(SizeX,SizeY,Planes&0xFF))!=NULL)
	InterlockedIncrement(&Raster->UsageCount);
  }
}


Image::Image(Raster2DAbstract *NewRaster): VecImage(NULL)
{
 RotAngle=x=y=dx=dy=0;
 Next=NULL;
 Palette = NULL;
 if((Raster=NewRaster)!=NULL) InterlockedIncrement(&Raster->UsageCount);
}


Image::Image(const Image & I, bool AllFrames)
{
 if((Raster=I.Raster)!=NULL) InterlockedIncrement(&Raster->UsageCount);
 if((Palette=I.Palette)!=NULL) InterlockedIncrement(&Palette->UsageCount);
 if((VecImage=I.VecImage)!=NULL) VecImage->UsageCount++;

 if(!I.Properties.isEmpty())
 {
    for(int i=0; i<I.Properties.PropCount; i++)
      Properties.AddProp(I.Properties.pProperties[i]);
 }

 x=I.x; y=I.y; dx=I.dx; dy=I.dy; RotAngle=I.RotAngle;
 Next = NULL;  

 if(AllFrames)
 {
   Image *PImg, *CurrImg;
   PImg = this;
   CurrImg = I.Next;
   while(CurrImg!=NULL)	//Multiple images
	{
	PImg->Next = new Image(*CurrImg,false);
	if(PImg->Next==NULL) break;	/*Allocation error*/
	PImg = PImg->Next;
	CurrImg = CurrImg->Next;
	}
 }
}


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


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

 PImg = this;

 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)
	    InterlockedIncrement(&CurrImg->Raster->UsageCount);
	if((PImg->Palette=CurrImg->Palette)!=NULL)
	    InterlockedIncrement(&CurrImg->Palette->UsageCount);
	PImg->x=CurrImg->x;PImg->y=CurrImg->y;
	PImg->dx=CurrImg->dx;PImg->dy=CurrImg->dy;
	PImg->RotAngle=CurrImg->RotAngle;
        if((PImg->VecImage=CurrImg->VecImage)!=NULL)
                CurrImg->VecImage->UsageCount++;
        if(!CurrImg->Properties.isEmpty())
	  {
          for(unsigned i=0; i<CurrImg->Properties.PropCount; i++)
            PImg->Properties.AddProp(CurrImg->Properties.pProperties[i]);
          }
	CurrImg = CurrImg->Next;
	}
 return(*this);
}


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

 I = this;
 while(I!=NULL)
   {
   if(I->Raster!=NULL)
      {
      if(InterlockedDecrement(&I->Raster->UsageCount)<=0) delete I->Raster;
      I->Raster = NULL;
      }
   if(I->Palette!=NULL)
      {
      if(InterlockedDecrement(&I->Palette->UsageCount)<=0) delete I->Palette;
      I->Palette = NULL;
      }
   if(I->VecImage!=NULL)
      {
      if(I->VecImage->UsageCount--<=1) delete I->VecImage;
      I->VecImage = NULL;
      }
   OldI = I;
   I = I->Next;
   OldI->Next = NULL;
   if(OldI!=this) delete(OldI);
   }
}


/** 0-none (ImageNone), 1-gray (ImageGray), 2-palette (ImagePalette), 3-true color (ImageTrueColor). */
IMAGE_TYPE Image::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;
  if(Raster->GetPlanes()==16)
  {
    if(Raster->Channels()>=3)  return ImageTrueColor;
  }
  return ImageGray;
}


uint32_t Image::GetPixelRGB(unsigned Offset1D, unsigned Offset2D) const
{
  if(Raster==NULL) return 0;
  switch(ImageType() & 0xFF00)
    {
    case ImageTrueColor:
	     return Raster->GetValue2D(Offset1D,Offset2D);
    case ImagePalette:
	     {
	     const uint32_t b = Raster->GetValue2D(Offset1D,Offset2D);
	     return Palette->GetValue1D(b);
	     }
    default: {
	     uint32_t b = Raster->GetValue2D(Offset1D,Offset2D);
	     switch(Raster->GetPlanes())
	     {
	       case   1: b *= 0xFF; break;
	       case   2: b *= 85; break;		// 255/3 = 85  -> 85*3=0xFF
	       case   4: b *= 17; break;		// 255/15=17   -> 17*15=0xFF
	       case  16: b >>= 8; break;
	       case  24: b >>= 16; break;
	       case  32:
	       case -32:				// floats are normalised to the full range 0 ... 0xFFFFFFFF.
	       case -64: 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(InterlockedDecrement(&Raster->UsageCount)<=0) delete Raster;
    Raster=NULL;
  }
  if(NewRaster!=NULL)			// Attach raster now
  {
    Raster=NewRaster; InterlockedIncrement(&Raster->UsageCount);
  }
}


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


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


void Image::AttachProperty(PropertyItem *NewProp)
{
  Properties.AddProp(NewProp);
}

void Image::AttachPropList(PropertyList &PropL)
{
  if(Properties.isEmpty())
  { 
    Properties.pProperties = PropL.pProperties;
    PropL.pProperties = NULL;
    Properties.PropCount = PropL.PropCount;
    PropL.PropCount = 0;
  }
}


bool Image::isEmpty(void) const
{
  if(Raster != NULL)
  {
    if(Raster->Size1D>0 && Raster->Size2D>0) return false;
  }
  if(VecImage != NULL)
  {
    if(VecImage->VectorObjects>0) return false;
  }
return true;
}


//////////////////////////////////////////////////////////


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

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


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(APalette *Palette, int Planes)
{
uint32_t i;
uint32_t PalItems;

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

 if(Planes==0) Planes = Palette->GetPlanes() / Palette->Channels();
 if(Planes >= sizeof(PalItems)*8) return 0; // Shift operation will overflow, consider image to be color.
 PalItems = (uint32_t)1<<Planes;
 if(Planes!=0)
   if(PalItems > Palette->GetSize1D())
	{
	//PalItems=Palette->Size1D/3;
	return(0);	//insufficient palette size, ?? non gray ??
	}

 const unsigned divider = (PalItems>1) ? (PalItems - 1) : 1;
 const unsigned multiplayer = (Planes>8) ? 65535 : 255;
 for(i=0; i<PalItems; i++)
	{
	const unsigned color = i*multiplayer / divider;
        RGBQuad RGB;
        Palette->Get(i,&RGB);
	if((RGB.R!=color) ||
	   (RGB.G!=color) ||
	   (RGB.B!=color) )
			return(0);
	}
 return(1);
}


/** Make this palette gray.
 * @param[in,out]	Palette	Palette to be set as gray.
 * @param[in]		Planes	If nonzeto, check for real planes per channel. */
int FillGray(Raster1DAbstractRGB *Palette, int Planes)
{
unsigned i;

 if(Palette==NULL) return(1);
 if(Palette->Data1D==NULL) return(0);

 if(Planes!=0)
   if(3*Planes != Palette->GetPlanes()) return(0);

 unsigned Divider = (Palette->Size1D-1);
 if(Divider==0) Divider=1;

 unsigned Multiplier = (Palette->GetPlanes()/Palette->Channels()<=8) ? 255 : 65535;

 for(i=0; i<Palette->Size1D; i++)
   {
   const unsigned color = i*Multiplier / Divider;
   Palette->setR(i,color);
   Palette->setG(i,color);
   Palette->setB(i,color);
   }
 return(1);
}


/** This fuction is intended to schrink image colors.
 * It can work in two modes: 
 *       1, 256 colors to 16 colors.
 *       2, True color to 256 colors.
 * @param[in,out]	Img	Image to be processed.
 * @param[in]		maxColors  Maximal color table size for true color images.
 * @return	0 - image was not changed; ColorCount - image has been reordered. */
int ReducePalette(Image *Img, unsigned maxColors)
{
unsigned x,y;
Raster1DAbstract *RowRas,*RowRas2;
APalette *paletettebuff;
unsigned CurrentColors = 0;
uint32_t RGBval, RGBval2;
unsigned i;
Raster2DAbstract *Raster2;

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

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

    Reindex = (uint8_t *)calloc(256,sizeof(uint8_t));
    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)		// We can schring 256 to 16 levels
      {
	/* 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(InterlockedDecrement(&Img->Raster->UsageCount) <= 0)
	  delete Img->Raster;
      Img->Raster = Raster2;
      InterlockedIncrement(&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;
}


//////////////////////////////////////////////////////////


/// Looks throuh whole image list chain for EXIF block.
bool HasExif(const Image *pI)
{
  while(pI != NULL)
  {
    unsigned i = 0;
    if(pI->Properties.Find("Exif",i) != NULL) return true;
    pI = pI->Next;
  }
return false;
}

#define TIFFTAG_ORIENTATION 274            /* +image orientation */

static uint32_t fLD_UINT32_LO(const unsigned char *Mem)
{
#ifdef WORDS_BIGENDIAN
  return (uint32_t)Mem[0] | ((uint32_t)Mem[1] << 8) | ((uint32_t)Mem[2] << 16) | ((uint32_t)Mem[3] << 24);
#else
  return *(uint32_t*)Mem;
#endif
}

static uint32_t fLD_UINT32_HI(const unsigned char *Mem)
{
  return ((uint32_t)Mem[0]<<24) | ((uint32_t)Mem[1] << 16) | ((uint32_t)Mem[2] << 8) | ((uint32_t)Mem[3]);
}

static uint16_t fLD_UINT16_LO(const unsigned char *Mem)
{
#ifdef WORDS_BIGENDIAN
  return (uint16_t)Mem[0] | ((uint16_t)Mem[1] << 8);
#else
  return *(uint16_t*)Mem;
#endif
}

static uint16_t fLD_UINT16_HI(const unsigned char *Mem)
{
  return ((uint16_t)Mem[0] << 8) | ((uint16_t)Mem[1]);
}


/// https://www.media.mit.edu/pia/Research/deepview/exif.html
class ExifParser
{
public:
	ExifParser(const unsigned char *pProfile, const uint32_t new_profile_length);

	const unsigned char *profile_data;
        const unsigned char *profile_end;
	
        bool isValid(void) const {return LD_UINT32!=NULL;}
        const unsigned char *FindExifField(uint16_t ReqTag, const unsigned char *IFD_data=NULL) const;

	uint32_t(*LD_UINT32)(const unsigned char *Mem);
	uint16_t(*LD_UINT16)(const unsigned char *Mem);
};


ExifParser::ExifParser(const unsigned char *pProfile, const uint32_t new_profile_length): profile_data(pProfile), profile_end(pProfile+new_profile_length)
{
unsigned char ByteOrder = 0;

  if(new_profile_length>=12+8 && pProfile!=NULL)
  {
    ByteOrder = *profile_data;
  }

  switch(ByteOrder)
  {
    case 'M': LD_UINT32 = fLD_UINT32_HI;
              LD_UINT16 = fLD_UINT16_HI;
              break;
    case 'I': LD_UINT32 = fLD_UINT32_LO;
              LD_UINT16 = fLD_UINT16_LO;
              break;
    default:  LD_UINT32 = NULL;
              LD_UINT16 = NULL;
              break;
  }
}


const unsigned char *ExifParser::FindExifField(uint16_t ReqTag, const unsigned char *IFD_data) const
{
uint16_t EntryNum;
uint16_t Tag;
uint32_t Value;

  if(!isValid()) return NULL;

  if(IFD_data==NULL)
  {
    if(profile_end-profile_data < 8) return NULL;
    Value = LD_UINT32(profile_data+4);
    IFD_data = profile_data + LD_UINT32(profile_data+4);
  }

  do
  {
    if(profile_end-IFD_data < 2) break;
    EntryNum = LD_UINT16(IFD_data);
    IFD_data+=2;
    if(profile_end-IFD_data < EntryNum*12) break;

    while(EntryNum>0)
    {
      Tag = (LD_UINT16)(IFD_data);

      if(Tag==ReqTag) return IFD_data;

      //Field = (LD_UINT16)(IFD_data+2);
      //Long2 = (LD_UINT32)(IFD_data+4);
      //Value = (LD_UINT32)(IFD_data+8);

      IFD_data += 12;
      EntryNum--;
    }

    if(profile_end-IFD_data < 4) break;
    Value = LD_UINT32(IFD_data);
    IFD_data = profile_data + Value;
  } while(Value>8);

  return NULL;
}


void AutoOrient(Image *pI)
{
  while(pI != NULL)
  {
    unsigned i = 0;
    const PropertyItem *PI = pI->Properties.Find("Exif",i);
    if(PI==NULL) break;

    ExifParser EP((const unsigned char *)PI->Data, PI->DataSize);
    const unsigned char *pIFD = EP.FindExifField(TIFFTAG_ORIENTATION);
    if(pIFD != NULL)
    {
      uint16_t Field;
      uint32_t Long2, Value;

      Field = (EP.LD_UINT16)(pIFD+2);
      Long2 = (EP.LD_UINT32)(pIFD+4);
      Value = (EP.LD_UINT32)(pIFD+8);

      if(Value>=5 && Value<=8)
      {
        Raster2DAbstract *rFlip = Flip1D2D(pI->Raster);
        if(rFlip) pI->AttachRaster(rFlip);
      }

      switch(Value)
      {
	case 5:				// The 0th row represents the visual left-hand side of the image, and the 0th column represents the visual top.
        default:
        case 0: 
        case 1: break;			// The 0th row represents the visual top of the image, and the 0th column represents the visual left-hand side.

	case 6:				// The 0th row represents the visual right-hand side of the image, and the 0th column represents the visual top.
	case 2: Flip1D(pI->Raster);	// The 0th row represents the visual top of the image, and the 0th column represents the visual right-hand side.
		break;
        case 7:				// The 0th row represents the visual right-hand side of the image, and the 0th column represents the visual bottom.
	case 3:	Flip2D(pI->Raster);	// The 0th row represents the visual bottom of the image, and the 0th column represents the visual right-hand side.
		Flip1D(pI->Raster);
		break;
	case 8:				// The 0th row represents the visual left-hand side of the image, and the 0th column represents the visual bottom. 
	case 4: Flip2D(pI->Raster);	// The 0th row represents the visual bottom of the image, and the 0th column represents the visual left-hand side.
		break;
      }
    }
    pI = pI->Next;
  }
}


