/*=============================================================================
                             pamenlarge
===============================================================================
  By Bryan Henderson 2004.09.26.  Contributed to the public domain by its
  author.
=============================================================================*/

#include "netpbm/mallocvar.h"
#include "netpbm/pm_c_util.h"
#include "netpbm/pam.h"
#include "netpbm/pbm.h"

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *inputFilespec;  
    unsigned int scaleFactor;
};



static void
parseCommandLine(int                  const 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.
-----------------------------------------------------------------------------*/
    if (argc-1 < 1)
        pm_error("You must specify at least one argument:  The scale factor");
    else {
        cmdlineP->scaleFactor = atoi(argv[1]);
        
        if (cmdlineP->scaleFactor < 1)
            pm_error("Scale factor must be an integer at least 1.  "
                     "You specified '%s'", argv[1]);

        if (argc-1 >= 2)
            cmdlineP->inputFilespec = argv[2];
        else
            cmdlineP->inputFilespec = "-";
    }
}        



static void
makeOutputRowMap(tuple **     const outTupleRowP,
                 struct pam * const outpamP,
                 struct pam * const inpamP,
                 tuple *      const inTuplerow) {
/*----------------------------------------------------------------------------
   Create a tuple *outTupleRowP which is actually a row of pointers into
   inTupleRow[], so as to map input pixels to output pixels by stretching.
-----------------------------------------------------------------------------*/
    tuple * newtuplerow;
    int col;

    MALLOCARRAY_NOFAIL(newtuplerow, outpamP->width);

    for (col = 0 ; col < inpamP->width; ++col) {
        unsigned int const scaleFactor = outpamP->width / inpamP->width;
        unsigned int subcol;

        for (subcol = 0; subcol < scaleFactor; ++subcol)
            newtuplerow[col * scaleFactor + subcol] = inTuplerow[col];
    }
    *outTupleRowP = newtuplerow;
}



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 - 2;
    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
enlargePbmRowHorizontally(struct pam *          const inpamP,
                          const unsigned char * const inrow,
                          unsigned int          const inColChars,
                          unsigned int          const outColChars,
                          unsigned int          const scaleFactor,
                          unsigned char *       const outrow) {

    static unsigned char const dbl[16] = { 
        0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F, 
        0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF };

    static unsigned char const trp1[8] = { 
        0x00, 0x03, 0x1C, 0x1F, 0xE0, 0xE3, 0xFC, 0xFF };
        
    static unsigned char const trp2[16] = { 
        0x00, 0x01, 0x0E, 0x0F, 0x70, 0x71, 0x7E, 0x7F,
        0x80, 0x81, 0x8E, 0x8F, 0xF0, 0xF1, 0xFE, 0xFF };

    static unsigned char const trp3[8] = { 
        0x00, 0x07, 0x38, 0x3F, 0xC0, 0xC7, 0xF8, 0xFF };

    static unsigned char const quad[4] = { 0x00, 0x0F, 0xF0, 0xFF };

    static unsigned char const quin2[8] = {
        0x00, 0x01, 0x3E, 0x3F, 0xC0, 0xC1, 0xFE, 0xFF };

    static unsigned char const quin4[8] = {
        0x00, 0x03, 0x7C, 0x7F, 0x80, 0x83, 0xFC, 0xFF };

    static unsigned int const pair[4] = { 0x0000, 0x00FF, 0xFF00, 0xFFFF };

    unsigned int colChar;

    switch (scaleFactor) {
    case 1:  break; /* outrow set to inrow */
    case 2:  /* Make outrow using prefabricated parts (same for 3, 5). */ 
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            outrow[colChar*2]   = dbl[(inrow[colChar] & 0xF0) >> 4];
            outrow[colChar*2+1] = dbl[(inrow[colChar] & 0x0F) >> 0];
            /* Possible outrow overrun by one byte. */
        }
        break;

    case 3:
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            outrow[colChar*3]   = trp1[(inrow[colChar] & 0xF0) >> 5];
            outrow[colChar*3+1] = trp2[(inrow[colChar] >> 2) & 0x0F];
            outrow[colChar*3+2] = trp3[(inrow[colChar] >> 0) & 0x07];
        }
        break;  

    case 4:
        for (colChar = 0; colChar < inColChars; ++colChar) {
            unsigned int i;
            for (i = 0; i < 4; ++i) 
                outrow[colChar*4+i]=
                    quad[(inrow[colChar] >> (6 - 2 * i)) & 0x03]; 
        }
        break;

    case 5:
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            outrow[colChar*5]   = pair [(inrow[colChar] >> 6) & 0x03] >> 5; 
            outrow[colChar*5+1] = quin2[(inrow[colChar] >> 4) & 0x07] >> 0; 
            outrow[colChar*5+2] = quad [(inrow[colChar] >> 3) & 0x03] >> 0; 
            outrow[colChar*5+3] = quin4[(inrow[colChar] >> 1) & 0x07] >> 0;
            outrow[colChar*5+4] = pair [(inrow[colChar] >> 0) & 0x03] >> 3; 
        }
        break;

    case 6:  /* Compound of 2 and 3 */ 
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            unsigned char const hi = dbl[(inrow[colChar] & 0xF0) >> 4];
            unsigned char const lo = dbl[(inrow[colChar] & 0x0F) >> 0];

            outrow[colChar*6]   = trp1[(hi & 0xF0) >> 5];
            outrow[colChar*6+1] = trp2[(hi >> 2) & 0x0F];
            outrow[colChar*6+2] = trp3[hi & 0x07];

            outrow[colChar*6+3] = trp1[(lo & 0xF0) >> 5];
            outrow[colChar*6+4] = trp2[(lo >> 2) & 0x0F];
            outrow[colChar*6+5] = trp3[lo & 0x07];
        }
        break;  

    case 7:
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            uint32_t hi, lo;

            hi = inrow[colChar] >> 4;
            hi = ((((hi>>1) * 0x00082080) | (0x01 & hi)) & 0x00204081 ) * 0x7F;
            hi >>= 4;
            outrow[colChar*7]   =  (unsigned char) ( hi >> 16);
            outrow[colChar*7+1] =  (unsigned char) ((hi >>  8) & 0xFF);
            outrow[colChar*7+2] =  (unsigned char) ((hi >>  0) & 0xFF);

            lo = inrow[colChar] & 0x001F;
            lo = ((((lo>>1) * 0x02082080) | (0x01 & lo)) & 0x10204081 ) * 0x7F;
            outrow[colChar*7+3] =  (unsigned char) ((lo >> 24) & 0xFF);
            outrow[colChar*7+4] =  (unsigned char) ((lo >> 16) & 0xFF); 
            outrow[colChar*7+5] =  (unsigned char) ((lo >>  8) & 0xFF);
            outrow[colChar*7+6] =  (unsigned char) ((lo >>  0) & 0xFF);
        }
        break;

    case 8:
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            unsigned int i;
            for (i = 0; i < 8; ++i) {
                outrow[colChar*8+i] =
                    ((inrow[colChar] >> (7-i)) & 0x01) *0xFF;
            }
        }
        break;

    case 9:
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            outrow[colChar*9]   =  ((inrow[colChar] >> 7) & 0x01) * 0xFF;
            outrow[colChar*9+1] =  pair[(inrow[colChar] >> 6) & 0x03] >> 1; 
            outrow[colChar*9+2] =  pair[(inrow[colChar] >> 5) & 0x03] >> 2; 
            outrow[colChar*9+3] =  pair[(inrow[colChar] >> 4) & 0x03] >> 3; 
            outrow[colChar*9+4] =  pair[(inrow[colChar] >> 3) & 0x03] >> 4; 
            outrow[colChar*9+5] =  pair[(inrow[colChar] >> 2) & 0x03] >> 5; 
            outrow[colChar*9+6] =  pair[(inrow[colChar] >> 1) & 0x03] >> 6; 
            outrow[colChar*9+7] =  pair[(inrow[colChar] >> 0) & 0x03] >> 7; 
            outrow[colChar*9+8] =  (inrow[colChar] & 0x01) * 0xFF;
        }
        break;

    case 10:
        for (colChar = 0; colChar < inColChars; ++colChar) { 
            outrow[colChar*10]   = ((inrow[colChar] >> 7) & 0x01 ) * 0xFF;
            outrow[colChar*10+1] = pair[(inrow[colChar] >> 6) & 0x03] >> 2; 
            outrow[colChar*10+2] = pair[(inrow[colChar] >> 5) & 0x03] >> 4; 
            outrow[colChar*10+3] = pair[(inrow[colChar] >> 4) & 0x03] >> 6;
            outrow[colChar*10+4] = ((inrow[colChar] >> 4) & 0x01) * 0xFF;
            outrow[colChar*10+5] = ((inrow[colChar] >> 3) & 0x01) * 0xFF; 
            outrow[colChar*10+6] = pair[(inrow[colChar] >> 2) & 0x03] >> 2; 
            outrow[colChar*10+7] = pair[(inrow[colChar] >> 1) & 0x03] >> 4; 
            outrow[colChar*10+8] = pair[(inrow[colChar] >> 0) & 0x03] >> 6; 
            outrow[colChar*10+9] = ((inrow[colChar] >> 0) & 0x01) * 0xFF;
        }
        break;
 

    default:
        /*  Unlike the above cases, we iterate through outrow.  To compute the
            color composition of each outrow byte, we consult a single bit or
            two consecutive bits in inrow.

            Color changes never happen twice in a single outrow byte.

            This is a generalization of above routines for scale factors
            9 and 10.

            Logic works for scale factors 4, 6, 7, 8, and above (but not 5).
        */

        for (colChar = 0; colChar < outColChars; ++colChar) {
            unsigned int const mult = scaleFactor;
            unsigned int const mod = colChar % mult;
            unsigned int const bit = (mod*8)/mult;
            /* source bit position, leftmost=0 */
            unsigned int const offset = mult - (mod*8)%mult;
            /* number of outrow bits derived from the same
               "source" inrow bit, starting at and to the right
               of leftmost bit of outrow byte, inclusive
            */

            if (offset >= 8)  /* Bits in outrow byte are all 1 or 0 */
                outrow[colChar] =
                    (inrow[colChar/mult] >> (7-bit) & 0x01) * 0xFF;
            else           /* Two inrow bits influence this outrow byte */ 
                outrow[colChar] = (unsigned char)
                    (pair[inrow[colChar/mult] >> (6-bit) & 0x03] >> offset)
                    & 0xFF;
        }
    }
}



static void
enlargePbm(struct pam * const inpamP,
           unsigned int const scaleFactor,
           FILE *       const ofP) {

    unsigned char * inrow;
    unsigned char * outrow;

    unsigned int row;

    unsigned int const outcols = inpamP->width * scaleFactor;
    unsigned int const outrows = inpamP->height * scaleFactor;
    unsigned int const inColChars  = pbm_packed_bytes(inpamP->width);
    unsigned int const outColChars = pbm_packed_bytes(outcols);

    inrow  = pbm_allocrow_packed(inpamP->width);
    
    if (scaleFactor == 1)
        outrow = inrow;
    else  {
        /* Allow writes beyond outrow data end when scaleFactor is
           one of the special fast cases: 2, 3, 4, 5, 6, 7, 8, 9, 10.
        */
        unsigned int const rightPadding = 
            scaleFactor > 10 ? 0 : (scaleFactor - 1) * 8;  
        outrow = pbm_allocrow_packed(outcols + rightPadding);
    }

    pbm_writepbminit(ofP, outcols, outrows, 0);
    
    for (row = 0; row < inpamP->height; ++row) {
        unsigned int i;

        pbm_readpbmrow_packed(inpamP->file, inrow, inpamP->width,
                              inpamP->format);

        if (outcols % 8 > 0)           /* clean final partial byte */ 
            pbm_cleanrowend_packed(inrow, inpamP->width);

        enlargePbmRowHorizontally(inpamP, inrow, inColChars, outColChars,
                                  scaleFactor, outrow);

        for (i = 0; i < scaleFactor; ++i)  
            pbm_writepbmrow_packed(ofP, outrow, outcols, 0);
    }
    
    if (outrow != inrow)
        pbm_freerow(outrow);

    pbm_freerow(inrow);
}



static void
enlargeGeneral(struct pam * const inpamP,
               unsigned int const scaleFactor,
               FILE *       const ofP) {
/*----------------------------------------------------------------------------
   Enlarge the input image described by *pamP.

   Assume the dimensions won't cause an arithmetic overflow.

   This works on all kinds of images, but is slower than enlargePbm on
   PBM.
-----------------------------------------------------------------------------*/
    struct pam outpam; 
    tuple * tuplerow;
    tuple * newtuplerow;
    unsigned int row;

    outpam = *inpamP; 
    outpam.file   = ofP;
    outpam.width  = inpamP->width  * scaleFactor;
    outpam.height = inpamP->height * scaleFactor; 

    pnm_writepaminit(&outpam);

    tuplerow = pnm_allocpamrow(inpamP);

    makeOutputRowMap(&newtuplerow, &outpam, inpamP, tuplerow);

    for (row = 0; row < inpamP->height; ++row) {
        pnm_readpamrow(inpamP, tuplerow);
        pnm_writepamrowmult(&outpam, newtuplerow, scaleFactor);
    }

    free(newtuplerow);

    pnm_freepamrow(tuplerow);
}



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

    struct cmdlineInfo cmdline;
    FILE * ifP;
    struct pam inpam;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFilespec);
 
    pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));
    
    validateComputableDimensions(inpam.width, inpam.height,
                                 cmdline.scaleFactor); 
    
    if (PNM_FORMAT_TYPE(inpam.format) == PBM_TYPE)
        enlargePbm(&inpam, cmdline.scaleFactor, stdout);
    else
        enlargeGeneral(&inpam, cmdline.scaleFactor, stdout);

    pm_close(ifP);
    pm_close(stdout);

    return 0;
}

