/*
* in_pcx.cpp: loads PCX images
* modified by [email protected] at Fri Apr 12 22:16:08 CEST 2002
* -- Fri Apr 12 23:54:57 CEST 2002
*
* xvpcx.c - load routine for PCX format pictures
*
* LoadPCX(fname, pinfo)  -  loads a PCX file
*/

/**** pts ****/
#include "config2.h"
#include "image.hpp"

#if USE_IN_PCX
#include "error.hpp"
#include "gensio.hpp"
#include <string.h>


/* Imp: palette handling etc. according to PCX_VER, see decode.c */
#define dimen Image::Sampled::dimen_t
#define pcxError(bname,conststr) Error::sev(Error::WARNING) << "PCX: " conststr << (Error*)0
#define WaitCursor()
#define xvbzero(p,len) memset(p, '\0', len)
#define FatalError(conststr) Error::sev(Error::EERROR) << "PCX: " conststr << (Error*)0
#define return_pcxError(bname, conststr) Error::sev(Error::EERROR) << "PCX: " conststr << (Error*)0
#define byte unsigned char
#define PCX_SIZE_T slen_t
#define malloc_byte(n) new byte[n]
#define PCX_FREE(p) delete [] (p)
/* the following list give indicies into saveColors[] array in xvdir.c */
#define F_FULLCOLOR 0
#define F_GREYSCALE 1
#define F_BWDITHER  2
/* values 'picType' can take */
#define PIC8  8
#define PIC24 24
#define xv_fopen(filename,read_mode) fopen(filename,"rb")
#define BaseName(x) ((char*)0)
#define PARM(parm) parm
/* info structure filled in by the LoadXXX() image reading routines */
#ifndef USE_PCX_DEBUG_MESSAGES
#define USE_PCX_DEBUG_MESSAGES 0
#endif

typedef struct { byte *pic;                  /* image data */
                dimen w, h;                 /* pic size */
#if 0 /**** pts ****/
                byte  r[256],g[256],b[256];
#else
                /* byte pal[3*256]; */
                byte *pal;
#  define PAL_R(pinfo,idx) (pinfo)->pal[3*(idx)]
#  define PAL_G(pinfo,idx) (pinfo)->pal[3*(idx)+1]
#  define PAL_B(pinfo,idx) (pinfo)->pal[3*(idx)+2]
#endif                                       /* colormap, if PIC8 */
#if 0 /**** pts ****/
                int   colType;              /* def. Color type to save in */
                int   type;                 /* PIC8 or PIC24 */
                int   normw, normh;         /* 'normal size' of image file
                                               (normally eq. w,h, except when
                                               doing 'quick' load for icons */
                int   frmType;              /* def. Format type to save in */
                char  fullInfo[128];        /* Format: field in info box */
                char  shrtInfo[128];        /* short format info */
                char *comment;              /* comment text */
                int   numpages;             /* # of page files, if >1 */
                char  pagebname[64];        /* basename of page files */
#endif
              } PICINFO;


/* #include "copyright.h" */

/*
* the following code has been derived from code written by
*  Eckhard Rueggeberg  ([email protected])
*/


/* #include "xv.h" */

/* offsets into PCX header */
#define PCX_ID      0
#define PCX_VER     1
#define PCX_ENC     2
#define PCX_BPP     3
#define PCX_XMINL   4
#define PCX_XMINH   5
#define PCX_YMINL   6
#define PCX_YMINH   7
#define PCX_XMAXL   8
#define PCX_XMAXH   9
#define PCX_YMAXL   10
#define PCX_YMAXH   11
                         /* hres (12,13) and vres (14,15) not used */
#define PCX_CMAP    16    /* start of 16*3 colormap data */
#define PCX_PLANES  65
#define PCX_BPRL    66
#define PCX_BPRH    67

#define PCX_MAPSTART 0x0c       /* Start of appended colormap   */


static int  pcxLoadImage8  PARM((char *, FILE *, PICINFO *, byte *));
static int  pcxLoadImage24 PARM((char *, FILE *, PICINFO *, byte *));
static void pcxLoadRaster  PARM((FILE *, byte *, int, byte *, dimen, dimen));
#if 0 /**** pts ****/
static int  pcxError       PARM((char *, char *));
#endif

static PCX_SIZE_T multiply_check(PCX_SIZE_T a, PCX_SIZE_T b) {
 const PCX_SIZE_T result = a * b;
 /* Check for overflow. Works only if everything is unsigned. */
 if (result / a != b) FatalError("Image too large.");
 return result;
}

static PCX_SIZE_T multiply_check(PCX_SIZE_T a, PCX_SIZE_T b, PCX_SIZE_T c) {
 return multiply_check(multiply_check(a, b), c);
}

/*******************************************/
static Image::Sampled *LoadPCX
#if 0 /**** pts ****/
 ___((char *fname, PICINFO *pinfo), (fname, pinfo), (char    *fname; PICINFO *pinfo;))
#else
 ___((FILE *fp, PICINFO *pinfo), (fname, pinfo), (char    *fname; PICINFO *pinfo;))
#endif
/*******************************************/
{
 Image::Sampled *ret=(Image::Sampled*)NULLP;
 byte   hdr[128];
#if 0 /**** pts ****/
 long   filesize;
 char  *bname;
 FILE  *fp;
 char *errstr; byte *image;
 int gray;
#endif
 int    i, colors, fullcolor;

 pinfo->pic     = (byte *) NULL;
 pinfo->pal     = (byte *) NULL;
#if 0 /**** pts ****/
 pinfo->type = PIC8;
 pinfo->comment = (char *) NULL;
 bname = BaseName(fname);

 /* open the stream */
 fp = xv_fopen(fname,"r");
 if (!fp) return_pcxError(bname, "unable to open file");
#endif

#if 0 /**** pts ****/
 /* figure out the file size */
 fseek(fp, 0L, 2);
 filesize = ftell(fp);
 fseek(fp, 0L, 0);
#endif

 /* read the PCX header */
 if (fread(hdr, (PCX_SIZE_T) 128, (PCX_SIZE_T) 1, fp) != 1 ||
     ferror(fp) || feof(fp)) {
   /* fclose(fp); */
   return_pcxError(bname, "EOF reached in PCX header.\n");
 }

 if (hdr[PCX_ID] != 0x0a || hdr[PCX_VER] > 5) {
   /* fclose(fp); */
   return_pcxError(bname,"unrecognized magic number");
 }

 pinfo->w = (hdr[PCX_XMAXL] + ((dimen) hdr[PCX_XMAXH]<<8))
          - (hdr[PCX_XMINL] + ((dimen) hdr[PCX_XMINH]<<8));

 pinfo->h = (hdr[PCX_YMAXL] + ((dimen) hdr[PCX_YMAXH]<<8))
          - (hdr[PCX_YMINL] + ((dimen) hdr[PCX_YMINH]<<8));

 pinfo->w++;  pinfo->h++;

 colors = 1 << (hdr[PCX_BPP] * hdr[PCX_PLANES]);
 fullcolor = (hdr[PCX_BPP] == 8 && hdr[PCX_PLANES] == 3);

#if USE_PCX_DEBUG_MESSAGES
 fprintf(stderr,"PCX: %dx%d image, version=%d, encoding=%d\n",
         pinfo->w, pinfo->h, hdr[PCX_VER], hdr[PCX_ENC]);
 fprintf(stderr,"   BitsPerPixel=%d, planes=%d, BytePerRow=%d, colors=%d\n",
         hdr[PCX_BPP], hdr[PCX_PLANES],
         hdr[PCX_BPRL] + ((dimen) hdr[PCX_BPRH]<<8),
         colors);
#endif

 if (colors>256 && !fullcolor) {
   /* fclose(fp); */
   return_pcxError(bname,"No more than 256 colors allowed in PCX file.");
 }

 if (hdr[PCX_ENC] != 1) {
   /* fclose(fp); */
   return_pcxError(bname,"Unsupported PCX encoding format.");
 }

 /* load the image, the image function fills in pinfo->pic */
 if (!fullcolor) {
   Image::Indexed *img=new Image::Indexed(pinfo->w, pinfo->h, colors, 8);
   pinfo->pal=(byte*)img->getHeadp();
   ASSERT_SIDE(pcxLoadImage8((char*)NULLP/*bname*/, fp, pinfo, hdr));
   memcpy(img->getRowbeg(), pinfo->pic, multiply_check(pinfo->w, pinfo->h));
   ret=img;
 } else {
   Image::RGB *img=new Image::RGB(pinfo->w, pinfo->h, 8);
   ASSERT_SIDE(pcxLoadImage24((char*)NULLP/*bname*/, fp, pinfo, hdr));
   memcpy(img->getRowbeg(), pinfo->pic, multiply_check(pinfo->w, pinfo->h, 3));
   ret=img;
 }
 PCX_FREE(pinfo->pic);
 pinfo->pic=(byte*)NULLP;


 if (ferror(fp) | feof(fp))    /* just a warning */
   pcxError(bname, "PCX file appears to be truncated.");

 if (colors>16 && !fullcolor) {       /* handle trailing colormap */
   while (1) {
     i=MACRO_GETC(fp);
     if (i==PCX_MAPSTART || i==EOF) break;
   }

#if 0 /**** pts ****/
   for (i=0; i<colors; i++) {
     PAL_R(pinfo,i) = MACRO_GETC(fp);
     PAL_G(pinfo,i) = MACRO_GETC(fp);
     PAL_B(pinfo,i) = MACRO_GETC(fp);
   }
#endif
   if (fread(pinfo->pal, 1, colors*3, fp) != colors * 3 + 0U ||
       ferror(fp) || feof(fp)) {
     pcxError(bname,"Error reading PCX colormap.  Using grayscale.");
     for (i=0; i<256; i++) PAL_R(pinfo,i) = PAL_G(pinfo,i) = PAL_B(pinfo,i) = i;
   }
 }
 else if (colors<=16) {   /* internal colormap */
#if 0 /**** pts ****/
   for (i=0; i<colors; i++) {
     PAL_R(pinfo,i) = hdr[PCX_CMAP + i*3];
     PAL_G(pinfo,i) = hdr[PCX_CMAP + i*3 + 1];
     PAL_B(pinfo,i) = hdr[PCX_CMAP + i*3 + 2];
   }
#else
   memcpy(pinfo->pal, hdr+PCX_CMAP, colors*3);
#endif
 }

 if (colors == 2) {    /* b&w */
#if 0 /**** pts ****/
   if (MONO(PAL_R(pinfo,0), PAL_G(pinfo,0), PAL_B(pinfo,0)) ==
       MONO(PAL_R(pinfo,1), PAL_G(pinfo,1), PAL_B(pinfo,1))) {
#else
   if (PAL_R(pinfo,0)==PAL_R(pinfo,1) && PAL_G(pinfo,0)==PAL_G(pinfo,1) && PAL_B(pinfo,0)==PAL_B(pinfo,1)) {
#endif
     /* create cmap */
     PAL_R(pinfo,0) = PAL_G(pinfo,0) = PAL_B(pinfo,0) = 255;
     PAL_R(pinfo,1) = PAL_G(pinfo,1) = PAL_B(pinfo,1) = 0;
#if USE_PCX_DEBUG_MESSAGES
     fprintf(stderr,"PCX: no cmap:  using 0=white,1=black\n");
#endif
   }
 }
 /* fclose(fp); */

 /* finally, convert into XV internal format */
#if 0 /**** pts ****/
 pinfo->type    = fullcolor ? PIC24 : PIC8;
 pinfo->frmType = -1;    /* no default format to save in */
#endif

#if 0 /**** pts ****/
 /* check for grayscaleitude */
 gray = 0;
 if (!fullcolor) {
   for (i=0; i<colors; i++) {
     if ((PAL_R(pinfo,i) != PAL_G(pinfo,i)) || (PAL_R(pinfo,i) != PAL_B(pinfo,i))) break;
   }
   gray = (i==colors) ? 1 : 0;
 }


 if (colors > 2 || (colors==2 && !gray)) {  /* grayscale or PseudoColor */
   pinfo->colType = (gray) ? F_GREYSCALE : F_FULLCOLOR;
#if 0 /**** pts ****/
   sprintf(pinfo->fullInfo,
           "%s PCX, %d plane%s, %d bit%s per pixel.  (%ld bytes)",
           (gray) ? "Greyscale" : "Color",
           hdr[PCX_PLANES], (hdr[PCX_PLANES]==1) ? "" : "s",
           hdr[PCX_BPP],    (hdr[PCX_BPP]==1) ? "" : "s",
           filesize);
#endif
 }
 else {
   pinfo->colType = F_BWDITHER;
#if 0 /**** pts ****/
   sprintf(pinfo->fullInfo, "B&W PCX.  (%ld bytes)", filesize);
#endif
 }

#if 0 /**** pts ****/
 sprintf(pinfo->shrtInfo, "%dx%d PCX.", pinfo->w, pinfo->h);
 pinfo->normw = pinfo->w;   pinfo->normh = pinfo->h;
#endif
#endif

 return ret;
}

/*****************************/
static int pcxLoadImage8 ___((char *fname, FILE *fp, PICINFO *pinfo, byte *hdr), (fname, fp, pinfo, hdr),
   (char    *fname;
    FILE    *fp;
    PICINFO *pinfo;
    byte    *hdr;))
{
 /* load an image with at most 8 bits per pixel */
 (void)fname; /**** pts ****/

 byte *image;

 image = (byte *) malloc_byte(multiply_check(pinfo->h, pinfo->w));
 if (!image) FatalError("Can't alloc 'image' in pcxLoadImage8()");

 xvbzero((char *) image, multiply_check(pinfo->h, pinfo->w));

 switch (hdr[PCX_BPP]) {
 case 1: case 2: case 4: case 8: pcxLoadRaster(fp, image, hdr[PCX_BPP], hdr, pinfo->w, pinfo->h);   break;
 default:
   PCX_FREE(image);
   return_pcxError(fname, "Unsupported # of bits per plane.");
 }

 pinfo->pic = image;
 return 1;
}


/*****************************/
static int pcxLoadImage24 ___((char *fname, FILE *fp, PICINFO *pinfo, byte *hdr), (fname, fp, pinfo, hdr),
   (char *fname;
    FILE *fp;
    PICINFO *pinfo;
    byte *hdr;))
{
 byte *pix, *pic24;
 int   c;
 unsigned i, j, w, h, cnt, planes, bperlin, nbytes;
#if 0 /***** pts ****/
 int maxv; /* ImageMagick does not have one */
 byte scale[256];
#endif

 (void)fname; /**** pts ****/

 w = pinfo->w;  h = pinfo->h;

 planes = (unsigned) hdr[PCX_PLANES];
 bperlin = hdr[PCX_BPRL] + ((dimen) hdr[PCX_BPRH]<<8);

 /* allocate 24-bit image */
 const PCX_SIZE_T alloced = multiply_check(w, h, planes);
 const PCX_SIZE_T w_planes = multiply_check(w, planes);
 pic24 = (byte *) malloc_byte(alloced);
 if (!pic24) FatalError("couldn't malloc 'pic24'");

 /* This may still fail with a segfault for large values of alloced, even
  * if malloc_byte has succeeded.
  */
 xvbzero((char *) pic24, alloced);
 fprintf(stderr, "AAA3\n");

#if 0 /**** pts ****/
 maxv = 0;
#endif
 pix = pinfo->pic = pic24;
 i = 0;      /* planes, in this while loop */
 j = 0;      /* bytes per line, in this while loop */
 nbytes = multiply_check(bperlin, h, planes);

 while (nbytes > 0 && (c = MACRO_GETC(fp)) != EOF) {
   if (c>=0xC0) {   /* have a rep. count */
     cnt = c & 0x3F;
     c = MACRO_GETC(fp);
     if (c == EOF) { MACRO_GETC(fp); break; }
   }
   else cnt = 1;
   if (cnt > nbytes) FatalError("Repeat count too large.");

#if 0 /**** pts ****/
   if (c > maxv)  maxv = c;
#endif

   while (cnt-- > 0) {
     if (j < w) {
       *pix = c;
       pix += planes;
     }
     j++;
     nbytes--;
     if (j == bperlin) {
       j = 0;
       if (++i < planes) {
         pix -= w_planes-1;  /* next plane on this line */
       }
       else {
         pix -= planes-1;    /* start of next line, first plane */
         i = 0;
       }
     }
   }
 }
 if (nbytes != 0) pcxError(0, "Image data truncated.");


#if 0 /**** pts ****/
 /* scale all RGB to range 0-255, if they aren't */

 if (maxv<255) {
   for (i=0; i<=maxv; i++) scale[i] = (i * 255) / maxv;

   for (i=0, pix=pic24; i<h; i++) {
     if ((i&0x3f)==0) WaitCursor();
     for (j=0; j<w_planes; j++, pix++) *pix = scale[*pix];
   }
 }
#endif

 return 1;
}



/*****************************/
static void pcxLoadRaster ___((FILE *fp, byte *image, int depth, byte *hdr, dimen w, dimen h), (fp, image, depth, hdr, w, h),
   (FILE    *fp;
    byte    *image, *hdr;
    int      depth;
    dimen w,h;))
{
 /* was supported:  8 bits per pixel, 1 plane, or 1 bit per pixel, 1-8 planes */

 unsigned row, bcnt, bperlin, pad, cnt, pmask, i, pleft;
 int b;
 byte *oldimage;

 bperlin = hdr[PCX_BPRL] + ((dimen) hdr[PCX_BPRH]<<8);
 pad = (depth == 1) ? bperlin * 8 : bperlin;
 if (pad < w) FatalError("pad too small");
 pad -= w;

 row = bcnt = 0;

 pmask = 1;  oldimage = image;
 pleft=hdr[PCX_PLANES];

 while ( (b=MACRO_GETC(fp)) != EOF) {
   if (b>=0xC0) {   /* have a rep. count */
     cnt = b & 0x3F;
     b = MACRO_GETC(fp);
     if (b == EOF) { MACRO_GETC(fp); return; }
   }
   else cnt = 1;

   for (i=0; i<cnt; i++) {
     switch (depth) {
      case 1:
       *image++|=(b&0x80)?pmask:0;
       *image++|=(b&0x40)?pmask:0;
       *image++|=(b&0x20)?pmask:0;
       *image++|=(b&0x10)?pmask:0;
       *image++|=(b&0x8)?pmask:0;
       *image++|=(b&0x4)?pmask:0;
       *image++|=(b&0x2)?pmask:0;
       *image++|=(b&0x1)?pmask:0;
       break;
      case 2: /**** pts ****/
       *image++|=((b>>6)&3)*pmask;
       *image++|=((b>>4)&3)*pmask;
       *image++|=((b>>2)&3)*pmask;
       *image++|=((b   )&3)*pmask;
       break;
      case 4: /**** pts ****/
       *image++|=((b>>4)&15)*pmask;
       *image++|=((b   )&15)*pmask;
       break;
      default:
       *image++=(byte)b;
     }

     bcnt++;

     if (bcnt == bperlin) {     /* end of a line reached */
       bcnt = 0;

       if (--pleft==0) {   /* moved to next row */
         pleft=hdr[PCX_PLANES];
         pmask=1;
         image -= pad;
         oldimage = image;
         row++;
         if (row >= h) return;   /* done */
       }
       else {   /* next plane, same row */
         image = oldimage;
         pmask<<=depth;
       }
     }
   }
 }
}

#if 0 /**** pts ****/
/*******************************************/
static int pcxError(fname,st)
    char *fname, *st;
{
 SetISTR(ISTR_WARNING,"%s:  %s", fname, st);
 return 0;
}
#endif

static Image::Sampled *in_pcx_reader(Image::Loader::UFD *ufd, SimBuffer::Flat const&) {
 PICINFO pinfo_;
 return LoadPCX(((Filter::UngetFILED*)ufd)->getFILE(/*seekable:*/false), &pinfo_);
}
static Image::Loader::reader_t in_pcx_checker(char buf[Image::Loader::MAGIC_LEN], char [Image::Loader::MAGIC_LEN], SimBuffer::Flat const&, Image::Loader::UFD*) {
 return buf[PCX_ID]==0x0a
     && (unsigned char)buf[PCX_VER]<=5
     && buf[PCX_ENC]==1
     && buf[PCX_BPP]<=8
      ? in_pcx_reader : 0;
}

#else
#define in_pcx_checker (Image::Loader::checker_t)NULLP
#endif /* USE_IN_PCX */

Image::Loader in_pcx_loader = { "PCX", in_pcx_checker, 0 };

/* __END__ */