#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcx.h>

static RGB egapal[16] = {
   {0, 0, 0}, {0, 0, 170}, {0, 170, 0}, {0, 170, 170},
   {170, 0, 0}, {170, 0, 170}, {170, 85, 0}, {170, 170, 170},
   {85, 85, 85}, {85, 85, 255}, {85, 255, 85}, {85, 255, 255},
   {255, 85, 85}, {255, 85, 255}, {255, 255, 85}, {255, 255, 255}
};

static RGB monopal[2] = {{0, 0, 0}, {255, 255, 255}};


static void planes_to_pixels(unsigned char *pixels, unsigned char *bitplanes,
       int bytesperline, int planes, int bitsperpixel)
{
   int i, j;
   int npixels;
   unsigned char *p;

   /* truecolor */
   if (bitsperpixel == 8) {
       unsigned char *bp = bitplanes;
       for (i = 0; i < 3; i++) {
           p = pixels + i;
           for (j = 0; j < bytesperline; j++, p += 3)
               *p = *(bp++);
       }
       return;
   }

  /* clear the pixel buffer */
   npixels = (bytesperline * 8) / bitsperpixel;
   p = pixels;
   while (--npixels >= 0)
       *p++ = 0;

  /* do the format conversion */
   for (i = 0; i < planes; i++) {
       int pixbit, bits, mask;

       p = pixels;
       pixbit = (1 << i);
       for (j = 0; j < bytesperline; j++) {
           bits = *bitplanes++;
           for (mask = 0x80; mask != 0; mask >>= 1, p++) {
               if (bits & mask)
                   *p |= pixbit;
           }
       }
   }
}

static void unpack_pixels(unsigned char *pixels, unsigned char *bitplanes,
       int bytesperline, int bitsperpixel)
{
   register int bits;

   if (bitsperpixel == 8) {
       while (--bytesperline >= 0)
           *pixels++ = *bitplanes++;
   } else if (bitsperpixel == 4) {
       while (--bytesperline >= 0) {
           bits = *bitplanes++;
           *pixels++ = (bits >> 4) & 0x0f;
           *pixels++ = bits & 0x0f;
       }
   } else if (bitsperpixel == 2) {
       while (--bytesperline >= 0) {
           bits = *bitplanes++;
           *pixels++ = (bits >> 6) & 0x03;
           *pixels++ = (bits >> 4) & 0x03;
           *pixels++ = (bits >> 2) & 0x03;
           *pixels++ = bits & 0x03;
       }
   } else if (bitsperpixel == 1) {
       while (--bytesperline >= 0) {
           bits = *bitplanes++;
           *pixels++ = ((bits & 0x80) != 0);
           *pixels++ = ((bits & 0x40) != 0);
           *pixels++ = ((bits & 0x20) != 0);
           *pixels++ = ((bits & 0x10) != 0);
           *pixels++ = ((bits & 0x08) != 0);
           *pixels++ = ((bits & 0x04) != 0);
           *pixels++ = ((bits & 0x02) != 0);
           *pixels++ = ((bits & 0x01) != 0);
       }
   }
}

static unsigned char *read_image(FILE *fp, int totbytes)
{
   unsigned char *buf, *bp;
   int c, count;

   buf = bp = (unsigned char *)malloc(totbytes);

   while (totbytes > 0) {
       if ((c = getc(fp)) == EOF) {
           fprintf(stderr, "Premature end of file\n");
           return NULL;
       }
       if ((c & 0xc0) != 0xc0) {
           *(bp++) = c;
            --totbytes;
           continue;
       }
       count = c & 0x3f;
       if ((c = getc(fp)) == EOF) {
           fprintf(stderr, "Premature end of file\n");
           return NULL;
       }
       if (count > totbytes) {
           fprintf(stderr, "Repeat count spans end of image, "
                   "count = %d, totbytes = %d\n", count, totbytes);
           return NULL;
       }
       totbytes -= count;
       while (--count >= 0)
           *(bp++) = c;
   }

   return buf;
}

RGB *read_pcx(char *filename, int *width, int *height)
{
   FILE *f;
   PCXHDR hdr;
   int w, h;
   RGB pal256[256], *pal = NULL;
   int truecolor = 0;
   unsigned char *image = NULL, *pixels = NULL;
   int linebytes;
   RGB *buf = NULL, *bp;
   int x, y;

   if (! (f = fopen(filename,"r")))
       return (0);

   if (fread(&hdr, 1, sizeof(PCXHDR), f) < sizeof(PCXHDR)) {
       fprintf(stderr, "Premature end of file\n");
       goto err;
   }

   switch (hdr.bitsperpixel) {
       case 1:
           if (hdr.planes > 4)
               goto fail;
           break;
       case 2:
       case 4:
           if (hdr.planes != 1)
               goto fail;
           break;
       case 8:
           if (hdr.planes != 1) {
               if (hdr.planes == 3 || hdr.planes == 4)
                   truecolor++;
               else
                   goto fail;
           }
           break;
       default:
          fail:
           fprintf(stderr, "Can't handle %d bits per pixel image with "
                   "%d planes\n", hdr.bitsperpixel, hdr.planes);
           goto err;
   }

   if (! truecolor) {
       if (hdr.bitsperpixel == 8) {
           fseek(f, -(sizeof(pal256) + 1), SEEK_END);
           if (getc(f) != 0x0c) {
               fprintf(stderr, "Bad color map signature\n");
               goto err;
           }
           fread(pal256, 1, sizeof(pal256), f);
           fseek(f, sizeof(PCXHDR), SEEK_SET);
           pal = pal256;
       } else {
           pal = (hdr.bitsperpixel == 1 && hdr.planes == 1 ? monopal :
                   (hdr.version == 0 || hdr.version == 3 ? egapal :
                   hdr.palette));
       }
   }

   w = (hdr.xmax - hdr.xmin) + 1; h = (hdr.ymax - hdr.ymin) + 1;

   linebytes = hdr.bytesperline * hdr.planes;

   if (! (image = read_image(f, linebytes * h)))
       goto err;

   buf = bp = (RGB *)malloc(w * sizeof(RGB) * h);

   pixels = (unsigned char *)malloc(truecolor ? hdr.bytesperline * 3 :
           hdr.bytesperline * 8 / hdr.bitsperpixel);

   for (y = 0; y < h; y++) {
       if (hdr.planes == 1)
           unpack_pixels(pixels, image + y * linebytes, hdr.bytesperline,
                   hdr.bitsperpixel);
       else
           planes_to_pixels(pixels, image + y * linebytes, hdr.bytesperline,
                   hdr.planes, hdr.bitsperpixel);
       if (truecolor) {
           RGB *p = (RGB *)pixels;
           for (x = 0; x < w; x++)
               *(bp++) = *(p++);
       } else {
           for (x = 0; x < w; x++)
               *(bp++) = pal[pixels[x]];
       }
   }

   *width = w; *height = h;

err:
   if (image) free(image);
   if (pixels) free(pixels);
   fclose(f);
   return buf;
}