/*

  Bayer matrix conversion tool

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 2 of the License,
  or (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA

  Copyright Alexandre Becoulet <diaxen AT free DOT fr>
  
  Completely rewritten for Netpbm by Bryan Henderson August 2005.
*/

#include <unistd.h>
#include <stdio.h>

#include "pm_c_util.h"
#include "pam.h"
#include "shhopt.h"
#include "mallocvar.h"
#include "nstring.h"


enum bayerType {
    BAYER1,
    BAYER2,
    BAYER3,
    BAYER4
};

struct cmdlineInfo {
    const char * inputFilespec;
    enum bayerType bayerType;
    unsigned int nointerpolate;
};



static void
parseCommandLine(int argc, const char ** 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 pm_optParseOptions3 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;
    unsigned int typeSpec;
    unsigned int type;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
    OPTENT3(0, "type",     OPT_UINT, &type,
            &typeSpec, 0);
    OPTENT3(0, "nointerpolate", OPT_FLAG, NULL,
            &cmdlineP->nointerpolate, 0);

    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)
        cmdlineP->inputFilespec = "-";
    else if (argc-1 > 1)
        pm_error("There is at most one argument -- the input file.  "
                 "You specified %u", argc-1);
    else
        cmdlineP->inputFilespec = argv[1];

    if (!typeSpec)
        pm_error("You must specify the -type option");
    else {
        switch (type) {
        case 1: cmdlineP->bayerType = BAYER1; break;
        case 2: cmdlineP->bayerType = BAYER2; break;
        case 3: cmdlineP->bayerType = BAYER3; break;
        case 4: cmdlineP->bayerType = BAYER4; break;
        }
    }
}



static void
calc_4(const struct pam * const pamP,
       tuple **           const intuples,
       tuple **           const outtuples,
       unsigned int       const plane,
       bool               const noInterpolation,
       unsigned int       const xoffset,
       unsigned int       const yoffset) {
/*----------------------------------------------------------------------------
    X . X
    . . .
    X . X

  For the Plane 'plane' sample values, an even pixel of outtuples[] gets the
  same value as intuples[][].  An odd pixel of outtuples[] gets the mean of
  the four surrounding even pixels, north, south, east, and west.  But zero if
  Caller says 'noInterpolation'.

  (even/odd is with respect to ('xoffset', 'yoffset')).
-----------------------------------------------------------------------------*/
    unsigned int row;
    
    /* Do the even rows -- the even column pixels get copied from the input,
       while the odd column pixels get the mean of adjacent even ones
    */
    for (row = yoffset; row < pamP->height; row += 2) {
        unsigned int col;
        for (col = xoffset; col + 2 < pamP->width; col += 2) {
            outtuples[row][col][plane] = intuples[row][col][0];
            outtuples[row][col + 1][plane] =
                noInterpolation ?
                0 :
                (intuples[row][col][0] + intuples[row][col + 2][0]) / 2;
        }
    }

    /* Do the odd rows -- every pixel is the mean of the one above and below */
    for (row = yoffset; row + 2 < pamP->height; row += 2) {
        unsigned int col;
        for (col = xoffset; col < pamP->width; ++col) {
            outtuples[row + 1][col][plane] = 
                noInterpolation ?
                0 :
                (outtuples[row][col][plane] +
                 outtuples[row + 2][col][plane]) / 2;
        }
    }
}



static void
calc_5(const struct pam * const pamP,
       tuple **           const intuples,
       tuple **           const outtuples,
       unsigned int       const plane,
       bool               const noInterpolation,
       unsigned int       const xoffset,
       unsigned int       const yoffset) {
/*----------------------------------------------------------------------------
   . X .
   X . X
   . X .

  For the Plane 'plane' sample values, an pixel on an even diagonal of
  outtuples[] gets the same value as intuples[][].  An pixel on an odd
  diagonal gets the mean of the four surrounding even pixels, north,
  south, east, and west.  But zero if Caller says 'noInterpolation'.

  (even/odd is with respect to ('xoffset', 'yoffset')).
-----------------------------------------------------------------------------*/
    unsigned int row;
    unsigned int j;

    j = 0;  /* initial value */

    for (row = yoffset; row + 2 < pamP->height; ++row) {
        unsigned int col;
        for (col = xoffset + j; col + 2 < pamP->width; col += 2) {
            outtuples[row][col + 1][plane] = intuples[row][col + 1][0];
            outtuples[row + 1][col + 1][plane] =
                noInterpolation ?
                0 :
                (intuples[row][col + 1][0] +
                 intuples[row + 1][col][0] +
                 intuples[row + 2][col + 1][0] +
                 intuples[row + 1][col + 2][0]) / 4;
        }
        j = 1 - j;
    }
}



struct compAction {
    unsigned int xoffset;
    unsigned int yoffset;
    void (*calc)(const struct pam * const pamP,
                 tuple **           const intuples,
                 tuple **           const outtuples,
                 unsigned int       const plane,
                 bool               const noInterpolation,
                 unsigned int       const xoffset,
                 unsigned int       const yoffset);
};



static struct compAction const comp_1[3] = {
/*----------------------------------------------------------------------------
  G B G B
  R G R G
  G B G B
  R G R G
-----------------------------------------------------------------------------*/

    { 0, 1, calc_4 },
    { 0, 1, calc_5 },
    { 1, 0, calc_4 }
};

static struct compAction const comp_2[3] = {
/*----------------------------------------------------------------------------
  R G R G
  G B G B
  R G R G
  G B G B
-----------------------------------------------------------------------------*/
    { 0, 0, calc_4 },
    { 0, 0, calc_5 },
    { 1, 1, calc_4 }
};

static struct compAction const comp_3[3] = {
/*----------------------------------------------------------------------------
  B G B G
  G R G R
  B G B G
  G R G R
-----------------------------------------------------------------------------*/
    { 1, 1, calc_4 },
    { 0, 0, calc_5 },
    { 0, 0, calc_4 }
};

static struct compAction const comp_4[3] = {
/*----------------------------------------------------------------------------
  G R G R
  B G B G
  G R G R
  B G B G
-----------------------------------------------------------------------------*/
    { 1, 0, calc_4 },
    { 0, 1, calc_5 },
    { 0, 1, calc_4 }
};



static void
makeOutputPam(const struct pam * const inpamP,
              struct pam *       const outpamP) {

    outpamP->size   = sizeof(*outpamP);
    outpamP->len    = PAM_STRUCT_SIZE(tuple_type);
    outpamP->file   = stdout;
    outpamP->format = PAM_FORMAT;
    outpamP->plainformat = 0;
    outpamP->width  = inpamP->width;
    outpamP->height = inpamP->height;
    outpamP->depth  = 3;
    outpamP->maxval = inpamP->maxval;
    outpamP->bytes_per_sample = inpamP->bytes_per_sample;
    STRSCPY(outpamP->tuple_type, "RGB");
}



static const struct compAction *
actionTableForType(enum bayerType const bayerType) {

    const struct compAction * retval;

    switch (bayerType) {
    case BAYER1: retval = comp_1; break;
    case BAYER2: retval = comp_2; break;
    case BAYER3: retval = comp_3; break;
    case BAYER4: retval = comp_4; break;
    }
    return retval;
}



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

    struct cmdlineInfo cmdline;
    FILE * ifP;
    struct pam inpam;
    struct pam outpam;
    tuple ** intuples;
    tuple ** outtuples;
    const struct compAction * compActionTable;
    unsigned int plane;

    pm_proginit(&argc, argv);
    
    parseCommandLine(argc, argv, &cmdline);
    
    ifP = pm_openr(cmdline.inputFilespec);
    
    intuples = pnm_readpam(ifP, &inpam, PAM_STRUCT_SIZE(tuple_type));

    compActionTable = actionTableForType(cmdline.bayerType);

    makeOutputPam(&inpam, &outpam);

    outtuples = pnm_allocpamarray(&outpam);

    for (plane = 0; plane < 3; ++plane) {
        struct compAction const compAction = compActionTable[plane];

        compAction.calc(&inpam, intuples, outtuples, plane,
                        cmdline.nointerpolate,
                        compAction.xoffset, compAction.yoffset);
    }
    pnm_writepam(&outpam, outtuples);

    pnm_freepamarray(outtuples, &outpam);
    pnm_freepamarray(intuples, &inpam);

    return 0;
}
