/*
** ppmtopict.c - read a portable pixmap and produce a Macintosh PICT2 file.
**
** Copyright (C) 1990 by Ken Yap <ken@cs.rochester.edu>.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include <assert.h>

#include "pm_c_util.h"
#include "pm.h"
#include "mallocvar.h"
#include "ppm.h"

#define HEADER_SIZE     512

#define RUN_THRESH      3
#define MAX_RUN         128     /* 0xff = 2, 0xfe = 3, etc */
#define MAX_COUNT       128     /* 0x00 = 1, 0x01 = 2, etc */

/* Opcodes */
#define PICT_NOP        0x00
#define PICT_clipRgn        0x01
#define PICT_bkPat      0x02
#define PICT_txFont     0x03
#define PICT_txFace     0x04
#define PICT_txMode     0x05
#define PICT_spExtra        0x06
#define PICT_pnSize     0x07
#define PICT_pnMode     0x08
#define PICT_pnPat      0x09
#define PICT_thePat     0x0A
#define PICT_ovSize     0x0B
#define PICT_origin     0x0C
#define PICT_txSize     0x0D
#define PICT_fgColor        0x0E
#define PICT_bkColor        0x0F
#define PICT_txRatio        0x10
#define PICT_picVersion     0x11
#define PICT_blPixPat       0x12
#define PICT_pnPixPat       0x13
#define PICT_fillPixPat     0x14
#define PICT_pnLocHFrac     0x15
#define PICT_chExtra        0x16
#define PICT_rgbFgCol       0x1A
#define PICT_rgbBkCol       0x1B
#define PICT_hiliteMode     0x1C
#define PICT_hiliteColor    0x1D
#define PICT_defHilite      0x1E
#define PICT_opColor        0x1F
#define PICT_line       0x20
#define PICT_line_from      0x21
#define PICT_short_line     0x22
#define PICT_short_line_from    0x23
#define PICT_long_text      0x28
#define PICT_DH_text        0x29
#define PICT_DV_text        0x2A
#define PICT_DHDV_text      0x2B
#define PICT_frameRect      0x30
#define PICT_paintRect      0x31
#define PICT_eraseRect      0x32
#define PICT_invertRect     0x33
#define PICT_fillRect       0x34
#define PICT_frameSameRect  0x38
#define PICT_paintSameRect  0x39
#define PICT_eraseSameRect  0x3A
#define PICT_invertSameRect 0x3B
#define PICT_fillSameRect   0x3C
#define PICT_frameRRect     0x40
#define PICT_paintRRect     0x41
#define PICT_eraseRRect     0x42
#define PICT_invertRRect    0x43
#define PICT_fillRRect      0x44
#define PICT_frameSameRRect 0x48
#define PICT_paintSameRRect 0x49
#define PICT_eraseSameRRect 0x4A
#define PICT_invertSameRRect    0x4B
#define PICT_fillSameRRect  0x4C
#define PICT_frameOval      0x50
#define PICT_paintOval      0x51
#define PICT_eraseOval      0x52
#define PICT_invertOval     0x53
#define PICT_fillOval       0x54
#define PICT_frameSameOval  0x58
#define PICT_paintSameOval  0x59
#define PICT_eraseSameOval  0x5A
#define PICT_invertSameOval 0x5B
#define PICT_fillSameOval   0x5C
#define PICT_frameArc       0x60
#define PICT_paintArc       0x61
#define PICT_eraseArc       0x62
#define PICT_invertArc      0x63
#define PICT_fillArc        0x64
#define PICT_frameSameArc   0x68
#define PICT_paintSameArc   0x69
#define PICT_eraseSameArc   0x6A
#define PICT_invertSameArc  0x6B
#define PICT_fillSameArc    0x6C
#define PICT_framePoly      0x70
#define PICT_paintPoly      0x71
#define PICT_erasePoly      0x72
#define PICT_invertPoly     0x73
#define PICT_fillPoly       0x74
#define PICT_frameSamePoly  0x78
#define PICT_paintSamePoly  0x79
#define PICT_eraseSamePoly  0x7A
#define PICT_invertSamePoly 0x7B
#define PICT_fillSamePoly   0x7C
#define PICT_frameRgn       0x80
#define PICT_paintRgn       0x81
#define PICT_eraseRgn       0x82
#define PICT_invertRgn      0x83
#define PICT_fillRgn        0x84
#define PICT_frameSameRgn   0x88
#define PICT_paintSameRgn   0x89
#define PICT_eraseSameRgn   0x8A
#define PICT_invertSameRgn  0x8B
#define PICT_fillSameRgn    0x8C
#define PICT_BitsRect       0x90
#define PICT_BitsRgn        0x91
#define PICT_PackBitsRect   0x98
#define PICT_PackBitsRgn    0x99
#define PICT_shortComment   0xA0
#define PICT_longComment    0xA1
#define PICT_EndOfPicture   0xFF
#define PICT_headerOp       0x0C00

#define MAXCOLORS 256



static void
putFill(FILE *       const ifP,
        unsigned int const n) {
/*----------------------------------------------------------------------------
   Write 'n' bytes of zeroes to *ifP.
-----------------------------------------------------------------------------*/
    unsigned int i;

    for (i = 0; i < n; ++i)
        putc(0, ifP);
}



static void
putShort(FILE * const ifP,
         int    const i) {
    putc((i >> 8) & 0xff, ifP);
    putc(i & 0xff, ifP);
}



static void
putLong(FILE * const ifP,
        long   const i) {
    putc((int)((i >> 24) & 0xff), ifP);
    putc(((int)(i >> 16) & 0xff), ifP);
    putc(((int)(i >> 8) & 0xff), ifP);
    putc((int)(i & 0xff), ifP);
}



static void
putFixed(FILE * const ifP,
         int    const in,
         int    const frac) {
    putShort(ifP, in);
    putShort(ifP, frac);
}



static void
putRect(FILE * const ifP,
        int    const x1,
        int    const x2,
        int    const y1,
        int    const y2) {
    putShort(ifP, x1);
    putShort(ifP, x2);
    putShort(ifP, y1);
    putShort(ifP, y2);
}



#define     runtochar(c)    (257-(c))
#define     counttochar(c)  ((c)-1)

static void
putRow(FILE *          const ofP,
       unsigned int    const row,
       unsigned int    const cols,
       pixel *         const rowpixels,
       colorhash_table const cht,
       unsigned char * const outBuf,
       unsigned int *  const outCountP) {
/*----------------------------------------------------------------------------
   Write the row rowpixels[], which is 'cols' pixels wide and is row 'row' of
   the image, to file *ofP in PICT format.

   Return as *outCountP the number of bytes we write to *ofP.

   Use buffer 'outBuf', which is at least 1.5*cols + 1 bytes -- the worst
   case compacted size of the row.
-----------------------------------------------------------------------------*/
    unsigned int i;
    unsigned int count;
    unsigned int run;
    unsigned int rep;
    unsigned int outCount;
    pixel lastpix;
    unsigned int outCursor;

    run = 0;  /* initial value */
    count = 0;  /* initial value */
    lastpix = rowpixels[cols-1];  /* initial value */

    for (i = 0, outCursor = 0; i < cols; ++i) {

        pixel const pix = rowpixels[cols - 1 - i];

        if (PPM_EQUAL(lastpix, pix))
            ++run;
        else if (run < RUN_THRESH) {
            while (run > 0) {
                outBuf[outCursor++] = ppm_lookupcolor(cht, &lastpix);
                --run;
                ++count;
                if (count == MAX_COUNT) {
                    outBuf[outCursor++] = counttochar(MAX_COUNT);
                    count -= MAX_COUNT;
                }
            }
            run = 1;
        } else {
            if (count > 0)
                outBuf[outCursor++] = counttochar(count);
            count = 0;
            while (run > 0) {
                rep = MIN(run, MAX_RUN);
                outBuf[outCursor++] = ppm_lookupcolor(cht, &lastpix);
                outBuf[outCursor++] = runtochar(rep);
                assert(run >= rep);
                run -= rep;
            }
            run = 1;
        }
        lastpix = pix;
    }
    if (run < RUN_THRESH) {
        while (run > 0) {
            outBuf[outCursor++] = ppm_lookupcolor(cht, &lastpix);
            --run;
            ++count;
            if (count == MAX_COUNT) {
                outBuf[outCursor++] = counttochar(MAX_COUNT);
                count -= MAX_COUNT;
            }
        }
    } else {
        if (count > 0)
            outBuf[outCursor++] = counttochar(count);
        count = 0;
        while (run > 0) {
            rep = MIN(run, MAX_RUN);
            outBuf[outCursor++] = ppm_lookupcolor(cht, &lastpix);
            outBuf[outCursor++] = runtochar(rep);
            assert(run >= rep);
            run -= rep;
        }
        run = 1;
    }
    if (count > 0)
        outBuf[outCursor++] = counttochar(count);

    /* Write out the packed row */

    if (cols-1 > 200) {
        putShort(ofP, outCursor);
        outCount = outCursor + 2;
    } else {
        putc(outCursor, ofP);
        outCount = outCursor + 1;
    }

    for (i = 0; i < outCursor; ++i)
        putc(outBuf[outCursor-i-1], ofP);

    *outCountP = outCount;
}



# if 0

/* real dumb putRow with no compression */
static void
putRow(FILE *          const ifP,
       unsigned int    const row,
       unsigned int    const cols,
       pixel *         const rowpixels,
       char *          const outBuf,
       colorhash_table const cht,
       unsigned int *  const outCountP) {

    unsigned int const bc = cols + (cols + MAX_COUNT - 1) / MAX_COUNT;

    unsigned int i;

    if (bc > 200) {
        putShort(ifP, bc);
        *outCountP = bc + 2;
    }  else {
        putc(bc, ifP);
        *outCountP = bc + 1;
    }
    for (col = 0; col < cols;) {
        if (cols - col > MAX_COUNT) {
            unsigned int j;
            putc(MAX_COUNT - 1, ifP);
            for (j = 0; j < MAX_COUNT; ++j) {
                putc(ppm_lookupcolor(cht, &rowPixels[col]), ifP);
                ++col
            }
            col += MAX_COUNT;
        } else {
            unsigned int j;
            putc(cols - col - 1, ifP);
            for (j = 0; j < cols - col; ++j) {
                putc(ppm_lookupcolor(cht, &rowPixels[col]), ifP);
                ++pP;
            }
            col = cols;
        }
    }
}
#endif  /* 0 */



static void
writeColorMap(FILE *           const ofP,
              colorhist_vector const chv,
              unsigned int     const colorCt,
              long             const lmaxval) {

    unsigned int i;

    for (i = 0; i < colorCt; ++i) {
        long rval, gval, bval;

        putShort(ofP, i);
        rval = PPM_GETR(chv[i].color);
        gval = PPM_GETG(chv[i].color);
        bval = PPM_GETB(chv[i].color);
        if (lmaxval != 65535L) {
            rval = rval * 65535L / lmaxval;
            gval = gval * 65535L / lmaxval;
            bval = bval * 65535L / lmaxval;
        }
        putShort(ofP, (short)rval);
        putShort(ofP, (short)gval);
        putShort(ofP, (short)bval);
    }
}



static void
writeRaster(FILE *          const ofP,
            pixel **        const pixels,
            unsigned int    const rows,
            unsigned int    const cols,
            colorhash_table const cht) {

    unsigned char * outBuf;  /* malloc'ed */
    unsigned int oc;
    unsigned int row;

    if (cols > UINT_MAX - cols/MAX_COUNT - 1) {
        /* We can't compute the size of buffer 'putRow' needs for worst-case
           compaction.
        */
        pm_error("Image is too wide (%u columns) for computation", cols);
    }

    MALLOCARRAY(outBuf, cols + cols/MAX_COUNT + 1);
    if (!outBuf)
        pm_error("Unable to allocate %u-byte row buffer",
                 cols + cols/MAX_COUNT + 1);

    for (row = 0, oc = 0; row < rows; ++row) {
        unsigned int rowSize;
        putRow(ofP, row, cols, pixels[row], cht, outBuf, &rowSize);
        oc += rowSize;
    }
    /* Pad to an even number of pixdata bytes */
    if (oc & 0x1)
        putc(0, ofP);

    free(outBuf);
}



int
main(int argc, const char ** argv) {

    FILE * ifP;
    int colorCt;
    int rows, cols;
    pixel ** pixels;
    pixval maxval;
    colorhist_vector chv;
    colorhash_table cht;
    long lmaxval;

    pm_proginit(&argc, argv);

    if (argc-1 > 0)
        ifP = pm_openr(argv[1]);
    else
        ifP = stdin;
    if (argc-1 > 1)
        pm_error("Too many arguments (%u).  The only argument is the "
                 "input file name", argc-1);

    pixels = ppm_readppm(ifP, &cols, &rows, &maxval);
    if (cols < 8)
        pm_error("ppm input too narrow, must be >= 8 pixels wide" );
    lmaxval = (long)maxval;
    pm_close(ifP);

    /* Figure out the colormap. */
    pm_message("computing colormap..." );
    chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colorCt);
    if (chv == NULL)
        pm_error("too many colors - try doing a 'pnmquant %u'", MAXCOLORS);
    pm_message("%u colors found", colorCt);

    /* Make a hash table for fast color lookup. */
    cht = ppm_colorhisttocolorhash(chv, colorCt);

    /* write the header */
    putFill(stdout, HEADER_SIZE);

    /* write picSize and picFrame */
    putShort(stdout, 0);  /* We overwrite this later when we know the size */
    putRect(stdout, 0, 0, rows, cols);

    /* write version op and version */
    putShort(stdout, PICT_picVersion);
    putShort(stdout, 0x02FF);
    putShort(stdout, PICT_headerOp);
    putLong(stdout, -1L);
    putFixed(stdout, 0, 0);
    putFixed(stdout, 0, 0);
    putFixed(stdout, cols, 0);
    putFixed(stdout, rows, 0);
    putFill(stdout, 4);

    /* seems to be needed by many PICT2 programs */
    putShort(stdout, PICT_clipRgn);
    putShort(stdout, 10);
    putRect(stdout, 0, 0, rows, cols);

    /* write picture */
    putShort(stdout, PICT_PackBitsRect);
    putShort(stdout, cols | 0x8000);
    putRect(stdout, 0, 0, rows, cols);
    putShort(stdout, 0);    /* pmVersion */
    putShort(stdout, 0);    /* packType */
    putLong(stdout, 0L);    /* packSize */
    putFixed(stdout, 72, 0);    /* hRes */
    putFixed(stdout, 72, 0);    /* vRes */
    putShort(stdout, 0);    /* pixelType */
    putShort(stdout, 8);    /* pixelSize */
    putShort(stdout, 1);    /* cmpCount */
    putShort(stdout, 8);    /* cmpSize */
    putLong(stdout, 0L);    /* planeBytes */
    putLong(stdout, 0L);    /* pmTable */
    putLong(stdout, 0L);    /* pmReserved */
    putLong(stdout, 0L);    /* ctSeed */
    putShort(stdout, 0);    /* ctFlags */
    putShort(stdout, colorCt-1); /* ctSize */

    writeColorMap(stdout, chv, colorCt, lmaxval);

    putRect(stdout, 0, 0, rows, cols);  /* srcRect */
    putRect(stdout, 0, 0, rows, cols);  /* dstRect */
    putShort(stdout, 0);            /* mode */

    writeRaster(stdout, pixels, rows, cols, cht);

    putShort(stdout, PICT_EndOfPicture);

    {
        /* patch size of pict in */
        long const picSz = ftell(stdout) - HEADER_SIZE;
        if (fseek(stdout, (long)HEADER_SIZE, 0) >= 0)
            putShort(stdout, (short)(picSz & 0xffff));
    }

    return 0;
}



