/* pbmpscale.c - pixel scaling with jagged edge smoothing.
 * AJCD 13/8/90
 */

#include <stdio.h>
#include "pm_c_util.h"
#include "mallocvar.h"
#include "shhopt.h"
#include "pbm.h"
#include "bitarith.h"

#define LEFTBITS pm_byteLeftBits
#define RIGHTBITS pm_byteRightBits

/* Table for translating bit pattern into "corners" flag element */

unsigned char const
transTable[512] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04,
     0x00, 0x00, 0x04, 0x04, 0xaa, 0xa2, 0x82, 0x82, 0x8a, 0x82, 0x82, 0x82,
     0xa0, 0xa0, 0x40, 0x40, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x10, 0x10,
     0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28,
     0x0a, 0x03, 0x01, 0x03, 0x0a, 0x03, 0x01, 0x03, 0xff, 0xff, 0xff, 0xff,
     0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0xa8, 0xa0, 0xc0, 0xc0,
     0x88, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80,
     0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28,
     0x00, 0x00, 0x28, 0x28, 0x0c, 0xff, 0xff, 0xff, 0x08, 0xff, 0xff, 0xff,
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
     0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x0a, 0x0a,
     0x28, 0x30, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x30, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0xa0, 0xa0,
     0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
     0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a,
     0x01, 0x01, 0x0a, 0x0a, 0x28, 0x30, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
     0x10, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0,
     0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff,
     0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x2a, 0x22, 0x03, 0x02,
     0x0a, 0x02, 0x03, 0x02, 0x30, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
     0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x28, 0x28,
     0x00, 0x00, 0x28, 0x28, 0x0a, 0x02, 0x03, 0x02, 0x0a, 0x02, 0x03, 0x02,
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04,
     0x28, 0x20, 0xff, 0xff, 0x08, 0xff, 0xff, 0xff, 0x30, 0x20, 0xff, 0xff,
     0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,
     0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28, 0x0c, 0xff, 0xff, 0xff,
     0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01, 0x0a, 0x0a,
     0x01, 0x01, 0x0a, 0x0a, 0x28, 0x20, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
     0x30, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0xa0, 0xa0,
     0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff, 0x82, 0x82, 0xff, 0xff,
     0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
     0x01, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x0a, 0x0a, 0x28, 0x20, 0x00, 0x00,
     0x08, 0x00, 0x00, 0x00, 0x30, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x40, 0x40, 0xa0, 0xa0, 0x40, 0x40, 0xa0, 0xa0, 0x82, 0x82, 0xff, 0xff,
     0x82, 0x82, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  };


/* starting positions for corners */
#define NE(f) ((f) & 3)
#define SE(f) (((f) >> 2) & 3)
#define SW(f) (((f) >> 4) & 3)
#define NW(f) (((f) >> 6) & 3)



struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    unsigned int scale;
    const char * inputFileName;  /* File name of input file */
};



static void
parseCommandLine(int argc, const char ** const argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.
-----------------------------------------------------------------------------*/
    optEntry * option_def;
        /* Instructions to OptParseOptions3 on how to parse our options.
         */
    optStruct3 opt;
    unsigned int option_def_index;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */

    OPTENTINIT;

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */

    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */

    if (argc-1 < 1)
        pm_error("You must specify the scale factor as an argument");
    else {
        int const scale = atoi(argv[1]);
        if (scale < 1)
            pm_error("Scale argument must be at least one.  You specified %d",
                     scale);
        else
            cmdlineP->scale = scale;

        if (argc-1 < 2)
            cmdlineP->inputFileName = "-";
        else {
            cmdlineP->inputFileName = argv[2];

            if (argc-1 > 2)
                pm_error("Too many arguments.  The only arguments are the "
                         "scale factor and optional input file name.  "
                         "You specified %u", argc-1);
        }
    }
    free(option_def);
}



static void
validateComputableDimensions(unsigned int const width,
                             unsigned int const height,
                             unsigned int const scaleFactor) {
/*----------------------------------------------------------------------------
   Make sure that multiplication for output image width and height do not
   overflow.
   See validateComputetableSize() in libpam.c
   and pbm_readpbminitrest() in libpbm2.c
-----------------------------------------------------------------------------*/
    unsigned int const maxWidthHeight = INT_MAX - 10;
    unsigned int const maxScaleFactor = maxWidthHeight / MAX(height, width);

    if (scaleFactor > maxScaleFactor)
       pm_error("Scale factor '%u' too large.  "
                "The maximum for this %u x %u input image is %u.",
                scaleFactor, width, height, maxScaleFactor);
}



static void
writeBitSpan(unsigned char * const packedBitrow,
             unsigned int    const cols,
             unsigned int    const offset,
             int             const color) {
/*----------------------------------------------------------------------------
   Write white (color="0") or black (="1") bits into packedBitrow[],
   starting at 'offset', length 'cols'.
-----------------------------------------------------------------------------*/
    unsigned char * const dest     = &packedBitrow[offset/8];
    unsigned int    const rs       = offset % 8;
    unsigned int    const trs      = (cols + rs) % 8;
    unsigned int    const colBytes = pbm_packed_bytes(cols + rs);
    unsigned int    const last     = colBytes - 1;

    unsigned char const origHead = dest[0];
    unsigned char const origEnd =  dest[last];

    unsigned int i;

    for (i = 0; i < colBytes; ++i)
        dest[i] = color * 0xff;

    if (rs > 0)
        dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs);

    if (trs > 0)
        dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs);
}



static void
setFlags(const bit *     const prevrow,
         const bit *     const thisrow,
         const bit *     const nextrow,
         unsigned char * const flags,
         unsigned int    const cols ) {
/*----------------------------------------------------------------------------
   Scan one row, examining the row above and row below, and determine
   whether there are "corners" for each pixel.  Feed a 9 bit sample into
   pre-calculated array transTable[512] to calculate all four corner statuses
   at once.

   Bits in the 9 bit sample represent the current pixel and neighbors:
       NW : N : NE : W: Current : E : SW : S : SE

   Bits in flag are divided into 4 fields of width 2 each:
       NW : SW : SE : NE

   Code 0xff is an exception.  It is a variation of 0x00.
     0x00 : no corners, no color change from above row (Current == N)
     0xff : no corners, but color changed (Current != N)

   Most transTable[] entries are "no corners".
   0x00 appears 180 times, 0xff 109 times.
-----------------------------------------------------------------------------*/

#if 0
    /* The following code is from the previous version, which examined
       the corners one by one:
    */

    /* list of corner patterns; bit 7 is current color, bits 0-6 are squares
       around (excluding square behind), going clockwise.
       The high byte of the patterns is a mask, which determines which bits are
       not ignored.
    */
    uint16_t const patterns[]
        = { 0x0000,   0xd555,            /* no corner */
            0x0001,   0xffc1, 0xd514,    /* normal corner */
            0x0002,   0xd554, 0xd515, 0xbea2, 0xdfc0, 0xfd81, 0xfd80, 0xdf80,
            /* reduced corners */
            0x0003,   0xbfa1, 0xfec2 };  /* reduced if cutoff > 1 */

    /*
      For example, the NE corner is examined with the following 8 bit sample:
      Current : W : NW : N : NE : E : SE : S
      (SW is the "square behind")
      */
#endif

    uint32_t prevrow24, thisrow24, nextrow24;
    unsigned int col;

    /* higher bits are set to 0 */
    prevrow24 = prevrow[0];  /* initial value */
    thisrow24 = thisrow[0];  /* initial value */
    nextrow24 = nextrow[0];  /* initial value */

    for (col = 0; col < cols; ++col) {
        unsigned int const col8   = col / 8;
        unsigned int const offset = col % 8;

        unsigned int sample;

        if (offset == 0) {
            prevrow24 = prevrow24 << 8 | prevrow[col8 + 1];
            thisrow24 = thisrow24 << 8 | thisrow[col8 + 1];
            nextrow24 = nextrow24 << 8 | nextrow[col8 + 1];
        }

        sample = ( ( prevrow24 >> ( 8 -offset) ) & 0x01c0 )
            | ( ( thisrow24 >> (11 -offset) ) & 0x0038 )
            | ( ( nextrow24 >> (14 -offset) ) & 0x0007 );

        flags[col] =  transTable[sample];
    }
}



static void
expandRow(const bit *     const thisrow,
          const bit *     const prevrow,
          bit *           const outrow,
          unsigned char * const flags,
          unsigned int    const cols,
          int             const scale,
          int             const cutoff,
          int             const ucutoff) {
/*----------------------------------------------------------------------------
  Process one row, using flags array as reference.  If pixel has no corners
  output a NxN square of the given color, otherwise output with the
  specified corner area(s) clipped off.
-----------------------------------------------------------------------------*/
    unsigned int const outcols = cols * scale;

    unsigned int i;
    unsigned int col;

    for (i = 0; i < scale; ++i) {
        int const zone = (i > ucutoff) - (i < cutoff);
        int const cut1 =
            (zone < 0) ? (cutoff - i) : (zone > 0) ? (i - ucutoff) : 0;

        unsigned int outcol;
        int cut[4];

        outcol = 0; /* initial value */

        cut[0] = 0;
        cut[1] = cut1;
        cut[2] = cut1 ? cut1 - 1 : 0;
        cut[3] = (cut1 && cutoff > 1) ? cut1 - 1 : cut1;

        for (col = 0; col < cols; ++col) {
            unsigned int const col8 = col / 8;
            unsigned int const offset = col % 8;
            int const pix = (thisrow[col8] >> (7-offset) ) & 0x01;
            int const flag = flags[col];

            int cutl, cutr;

            if (flag == 0x00) {
                /* There are no corners, no color change */
                outcol += scale;
            } else {
                switch (zone) {
                case -1:
                    if (i==0 && flag == 0xff) {
                        /* No corners, color changed */
                        cutl = cutr = 0;
                        flags[col] = 0x00;
                            /* Use above skip procedure next cycle */
                    } else {
                        cutl = cut[NW(flag)];
                        cutr = cut[NE(flag)];
                    }
                    break;
                case 0:
                    cutl = cutr = 0;
                    break ;
                case 1:
                    cutl = cut[SW(flag)];
                    cutr = cut[SE(flag)];
                    break;
                }

                if (cutl > 0) {
                    writeBitSpan(outrow, cutl, outcol, !pix);
                    outcol += cutl;
                }
                {
                    unsigned int const center = scale - cutl - cutr;

                    if (center > 0) {
                        writeBitSpan(outrow, center, outcol,  pix);
                        outcol += center;
                    }
                }
                if (cutr > 0) {
                    writeBitSpan(outrow, cutr, outcol, !pix);
                    outcol += cutr;
                }
            }
        }
        pbm_writepbmrow_packed(stdout, outrow, outcols, 0) ;
    }
}



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

    struct cmdlineInfo cmdline;
    FILE * ifP;
    bit ** buffer;
    bit * prevrow;
    bit * thisrow;
    bit * nextrow;
    bit * edgerow;
    bit * outrow;
    unsigned int row;
    unsigned int i;
    int cols, rows;
    int format;
    unsigned int outcols;
    unsigned int outrows;
    int cutoff;
    int ucutoff ;
    unsigned char * flags;  /* malloc'ed */

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFileName);

    pbm_readpbminit(ifP, &cols, &rows, &format) ;

    validateComputableDimensions(cols, rows, cmdline.scale);

    outcols = cols * cmdline.scale;
    outrows = rows * cmdline.scale;

    /* Initialize input buffers.
       We add a margin of 8 bits on the right of the three rows.

       On the top and bottom of the image we place an imaginary blank row
       ("edgerow") to facilitate the process.
    */

    buffer  = pbm_allocarray_packed(cols + 8, 3);
    edgerow = pbm_allocrow_packed(cols + 8);

    for (i = 0; i < pbm_packed_bytes(cols + 8); ++i)
        edgerow[i] = 0x00;

    /* Add blank bytes at right edges */
    for (i = 0; i < 3; ++i)
        buffer[i][pbm_packed_bytes(cols + 8) - 1] = 0x00;

    thisrow = edgerow;
    nextrow = buffer[0];

    /* Read the top line into nextrow and clean the right end. */

    pbm_readpbmrow_packed(ifP, nextrow, cols, format);
    pbm_cleanrowend_packed(nextrow, cols);

    outrow = pbm_allocrow_packed(outcols);
    for (i = 0; i < pbm_packed_bytes(outcols); ++i)
        outrow[i] = 0x00;

    MALLOCARRAY(flags, cols);
    if (flags == NULL)
        pm_error("Couldn't get memory for %u columns of flags", cols);

    pbm_writepbminit(stdout, outcols, outrows, 0) ;

    cutoff = cmdline.scale / 2;
    ucutoff = cmdline.scale - 1 - cutoff;

    for (row = 0; row < rows; ++row) {
        prevrow = thisrow;  /* Slide up the input row window */
        thisrow = nextrow;
        if (row < rows - 1) {
            nextrow = buffer[(row + 1) % 3];
            /* We take the address directly instead of shuffling the rows.
               This provision is for proper handling of the initial edgerow.
            */
            pbm_readpbmrow_packed(ifP, nextrow, cols, format);
            pbm_cleanrowend_packed(nextrow, cols);
        } else
            /* Bottom of image.  */
            nextrow = edgerow;

        setFlags(prevrow, thisrow, nextrow, flags, cols);

        expandRow(thisrow, prevrow, outrow, flags, cols, cmdline.scale,
                  cutoff, ucutoff);
    }
    pbm_freearray(buffer,3);
    pbm_freerow(edgerow);
    pbm_freerow(outrow);
    free (flags);
    pm_close(ifP);
    return 0;
}
