/*----------------------------------------------------------------------------
                                  libpamn.c
------------------------------------------------------------------------------
   These are the library functions, which belong in the libnetpbm library,
   that deal with the PAM image format via maxval-normalized, floating point
   sample values.
-----------------------------------------------------------------------------*/

#include <assert.h>

#include "pam.h"
#include "mallocvar.h"
#include "fileio.h"
#include "pm_gamma.h"

#define EPSILON 1e-7



tuplen *
pnm_allocpamrown(const struct pam * const pamP) {
/*----------------------------------------------------------------------------
   We assume that the dimensions of the image are such that arithmetic
   overflow will not occur in our calculations.  NOTE: pnm_readpaminit()
   ensures this assumption is valid.
-----------------------------------------------------------------------------*/
    const int bytes_per_tuple = pamP->depth * sizeof(samplen);
    tuplen * tuplerown;

    /* The tuple row data structure starts with 'width' pointers to
       the tuples, immediately followed by the 'width' tuples
       themselves.  Each tuple consists of 'depth' samples.
    */

    tuplerown = malloc(pamP->width * (sizeof(tuplen *) + bytes_per_tuple));
    if (tuplerown == NULL)
        pm_error("Out of memory allocating space for a tuple row of\n"
                 "%d tuples by %d samples per tuple by %d bytes per sample.",
                 pamP->width, pamP->depth, sizeof(samplen));

    {
        /* Now we initialize the pointers to the individual tuples to make this
           a regulation C two dimensional array.
        */
        
        char *p;
        int i;
        
        p = (char*) (tuplerown + pamP->width);  /* location of Tuple 0 */
        for (i = 0; i < pamP->width; i++) {
            tuplerown[i] = (tuplen) p;
            p += bytes_per_tuple;
        }
    }
    return(tuplerown);
}



static bool
plain(int const format) {

    switch (format) {
    case PAM_FORMAT:
    case RPPM_FORMAT:
    case RPGM_FORMAT:
    case RPBM_FORMAT:
        return FALSE;
        break;
    default:
        return TRUE;
    }
}



void 
pnm_readpamrown(const struct pam * const pamP, 
                tuplen *           const tuplenrow) {

    /* For speed, we don't check any of the inputs for consistency 
       here (unless it's necessary to avoid crashing).  Any consistency
       checking should have been done by a prior call to 
       pnm_writepaminit().
    */
    assert(pamP->maxval != 0);

    /* Need a special case for raw PBM because it has multiple tuples (8)
       packed into one byte.
    */
    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
        int col;
        bit *bitrow;
        if (pamP->depth != 1)
            pm_error("Invalid pam structure passed to pnm_readpamrow().  "
                     "It says PBM format, but 'depth' member is not 1.");
        bitrow = pbm_allocrow(pamP->width);
        pbm_readpbmrow(pamP->file, bitrow, pamP->width, pamP->format);
        for (col = 0; col < pamP->width; col++)
            tuplenrow[col][0] = 
                bitrow[col] == PBM_BLACK ? 0.0 : 1.0;
        pbm_freerow(bitrow);
    } else {
        int col;
        for (col = 0; col < pamP->width; col++) {
            int samp;
            for (samp = 0; samp < pamP->depth; samp++) {
                if (plain(pamP->format))
                    tuplenrow[col][samp] = 
                        (float)pm_getuint(pamP->file) / pamP->maxval;
                else 
                    tuplenrow[col][samp] = 
                        (float)pm_getraw(pamP->file, pamP->bytes_per_sample) /
                        pamP->maxval;
            }
        }
    }
}



void 
pnm_writepamrown(const struct pam * const pamP, 
                 const tuplen *     const tuplenrow) {

    /* For speed, we don't check any of the inputs for consistency 
       here (unless it's necessary to avoid crashing).  Any consistency
       checking should have been done by a prior call to 
       pnm_writepaminit().
    */
    assert(pamP->maxval != 0);

    /* Need a special case for raw PBM because it has multiple tuples (8)
       packed into one byte.
       */
    if (PAM_FORMAT_TYPE(pamP->format) == PBM_TYPE) {
        int col;
        bit *bitrow;
        bitrow = pbm_allocrow(pamP->width);
        for (col = 0; col < pamP->width; col++)
            bitrow[col] = 
                tuplenrow[col][0] < 0.5 ? PBM_BLACK : PBM_WHITE;
        pbm_writepbmrow(pamP->file, bitrow, pamP->width, 0);
        pbm_freerow(bitrow);
    } else {
        tuple * tuplerow;
        int col;

        tuplerow = pnm_allocpamrow(pamP);

        for (col = 0; col < pamP->width; ++col) {
            unsigned int plane;
            for (plane = 0; plane < pamP->depth; ++plane)
                tuplerow[col][plane] = (sample)
                    (tuplenrow[col][plane] * pamP->maxval + 0.5);
        }    
        pnm_writepamrow(pamP, tuplerow);
        pnm_freepamrow(tuplerow);
    }
}



tuplen **
pnm_allocpamarrayn(const struct pam * const pamP) {
    
    tuplen **tuplenarray;
    int row;

    /* If the speed of this is ever an issue, it might be sped up a little
       by allocating one large chunk.
    */
    
    MALLOCARRAY(tuplenarray, pamP->height);
    if (tuplenarray == NULL) 
        pm_error("Out of memory allocating the row pointer section of "
                 "a %u row array", pamP->height);

    for (row = 0; row < pamP->height; row++) {
        tuplenarray[row] = pnm_allocpamrown(pamP);
    }
    return(tuplenarray);
}



void
pnm_freepamarrayn(tuplen **          const tuplenarray, 
                  const struct pam * const pamP) {

    int row;
    for (row = 0; row < pamP->height; row++)
        pnm_freepamrown(tuplenarray[row]);

    free(tuplenarray);
}



tuplen** 
pnm_readpamn(FILE *       const file, 
             struct pam * const pamP, 
             int          const size) {

    tuplen **tuplenarray;
    int row;

    pnm_readpaminit(file, pamP, size);
    
    tuplenarray = pnm_allocpamarrayn(pamP);
    
    for (row = 0; row < pamP->height; row++) 
        pnm_readpamrown(pamP, tuplenarray[row]);

    return(tuplenarray);
}



void 
pnm_writepamn(struct pam * const pamP, 
              tuplen **    const tuplenarray) {

    int row;

    pnm_writepaminit(pamP);
    
    for (row = 0; row < pamP->height; row++) 
        pnm_writepamrown(pamP, tuplenarray[row]);
}



void
pnm_normalizetuple(struct pam * const pamP,
                   tuple        const tuple,
                   tuplen       const tuplen) {

    unsigned int plane;

    for (plane = 0; plane < pamP->depth; ++plane) 
        tuplen[plane] = (samplen)tuple[plane] / pamP->maxval;
}



void
pnm_unnormalizetuple(struct pam * const pamP,
                     tuplen       const tuplen,
                     tuple        const tuple) {

    unsigned int plane;

    for (plane = 0; plane < pamP->depth; ++plane) 
        tuple[plane] = tuplen[plane] * pamP->maxval + 0.5;
}



typedef samplen (*gammaFunction)(samplen);

static void
gammaCommon(struct pam *  const pamP,
            tuplen *      const tuplenrow,
            gammaFunction       gammafn) {

    unsigned int plane;
    unsigned int opacityPlane;
    bool haveOpacity;
    
    pnm_getopacity(pamP, &haveOpacity, &opacityPlane);

    for (plane = 0; plane < pamP->depth; ++plane) {
        if (haveOpacity && plane == opacityPlane) {
            /* It's an opacity (alpha) plane, which means there is
               no gamma adjustment in it.  
            */
        } else {
            unsigned int col;
            for (col = 0; col < pamP->width; ++col)
                tuplenrow[col][plane] = gammafn(tuplenrow[col][plane]);
        }
    }
}



void
pnm_gammarown(struct pam * const pamP,
              tuplen *     const tuplenrow) {

    gammaCommon(pamP, tuplenrow, &pm_gamma709);
}



void
pnm_ungammarown(struct pam * const pamP,
                tuplen *     const tuplenrow) {

    gammaCommon(pamP, tuplenrow, &pm_ungamma709);
}



enum applyUnapply {OPACITY_APPLY, OPACITY_UNAPPLY};

static void
applyopacityCommon(enum applyUnapply const applyUnapply,
                   struct pam *      const pamP,
                   tuplen *          const tuplenrow) {

    unsigned int opacityPlane;
    bool haveOpacity;
    
    pnm_getopacity(pamP, &haveOpacity, &opacityPlane);

    if (haveOpacity) {
        unsigned int plane;
        for (plane = 0; plane < pamP->depth; ++plane) {
            if (plane != opacityPlane) {
                unsigned int col;
                for (col = 0; col < pamP->width; ++col) {
                    tuplen const thisTuple = tuplenrow[col];

                    switch (applyUnapply) {
                    case OPACITY_APPLY:
                        thisTuple[plane] *= thisTuple[opacityPlane];
                        break;
                    case OPACITY_UNAPPLY:
                        if (thisTuple[opacityPlane] < EPSILON)
                            /* There is no foreground here at all.  So
                               the color plane values must be zero and
                               as output it makes absolutely no
                               difference what they are (they must be
                               multiplied by the opacity -- zero -- to
                               be used).
                            */
                            assert(thisTuple[plane] < EPSILON);
                        else
                            thisTuple[plane] /= thisTuple[opacityPlane];
                        break;
                    }
                }
            }
        }
    }
}


void
pnm_applyopacityrown(struct pam * const pamP,
                     tuplen *     const tuplenrow) {

    applyopacityCommon(OPACITY_APPLY, pamP, tuplenrow);

}



void
pnm_unapplyopacityrown(struct pam * const pamP,
                       tuplen *     const tuplenrow) {

    applyopacityCommon(OPACITY_UNAPPLY, pamP, tuplenrow);
}
