/*----------------------------------------------------------------------------
                                  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"


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);
}



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 (pamP->plainformat)
                    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 {
        int col;

        for (col = 0; col < pamP->width; col++) {
            int samp;
            for (samp = 0; samp < pamP->depth; samp++) {
                /* If we allowed writing of plain format via the pam functions,
                   we could do a putus() here to write a plain sample.  We'd
                   also need some stuff to put in the spaces and line breaks.
                */
                pm_putraw(pamP->file, 
                          (sample) 
                          (tuplenrow[col][samp] * pamP->maxval + 0.5), 
                          pamP->bytes_per_sample);
            }
        }    
    }
}



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] = pm_ungamma709(tuple[plane]);
}



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] = pm_gamma709(tuplen[plane]);
}
