/* pnminterp - scale up portable anymap by interpolating between pixels.
 * Copyright (C) 1998,2000 Russell Marks.
 *
 * 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#define _POSIX_C_SOURCE 2
    /* Make sure getopt() is in unistd.h */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include "pnm.h"


/* prototypes */
void usage(void);
static void interp(FILE *in, const int cols, const int rows, 
                   const xelval maxval, const int format,
                   const int output_format,
                   const int scale, const int edge_mode);
static void 
write_interp_line(const xel * const curline,
                  const xel * const nextline, 
                  xel * const outbuf,
                  const int cols,const xelval maxval, const int format,
                  const int scale, const int edge_mode);

/* drop one (source) pixel at right/bottom edges. */
#define EDGE_DROP		1

/* interpolate right/bottom edge pixels to black. */
#define EDGE_INTERP_TO_BLACK	2

/* don't interpolate right/bottom edge pixels (default, and what zgv does). */
#define EDGE_NON_INTERP		3

int edge_mode=EDGE_NON_INTERP;



int main(int argc,char *argv[])
{
FILE *in=stdin;

int rows, cols, format, output_format;
xelval maxval;
int scale;
int done=0,left;

pnm_init(&argc, argv);

opterr=0;

do
  switch(getopt(argc,argv,"bdh"))
    {
    case 'b':	/* interpolate to black at right/bottom edges */
      edge_mode=EDGE_INTERP_TO_BLACK; break;
    case 'd':	/* drop right/bottom edges */
      edge_mode=EDGE_DROP; break;
    case 'h':	/* help on usage */
      usage();		/* doesn't return */
    case '?':
      fprintf(stderr,"pnminterp: unrecognised option `%c'.\n",optopt);
      exit(1);
    case -1:
      done=1;
    }
while(!done);

/* must be at least one arg left, to specify scale,
 * but no more than two args left (scale and filename).
 */
left=argc-optind;
if(left<1 || left>2)
  usage();

scale=atoi(argv[argc-left]);
if(scale<2)
  fprintf(stderr,"pnminterp: scale must be at least 2.\n"),exit(1);

if(left==2)
  in=pm_openr(argv[argc-1]);

pnm_readpnminit(in,&cols,&rows,&maxval,&format);

if(cols==1 || rows==1)
  fprintf(stderr,"pnminterp: width/height must both be >=2.\n"),exit(1);

if(PNM_FORMAT_TYPE(format)==PBM_TYPE) {
  output_format=PGM_TYPE;
  /* usual filter message when reading PBM but writing PGM: */
  fprintf(stderr,"pnminterp: promoting from PBM to PGM\n");
} else {
  output_format=PPM_TYPE;
}
pnm_writepnminit(stdout,
                 (cols-(edge_mode==EDGE_DROP))*scale,
                 (rows-(edge_mode==EDGE_DROP))*scale,
                 maxval, output_format, 0);

interp(in,cols,rows,maxval,format,output_format,scale,edge_mode);

pm_close(in);

exit(0);
}



void usage(void)
{
printf("usage: pnminterp [-bdh] N [pnmfile]\n\
\n\
	-b	interpolate to black at right/bottom edges\n\
	-d	drop (scaled-up) pixels at right/bottom edges\n\
	-h	give this usage help\n\
\n\
	N	the amount to scale up the width and height of the\n\
		input image; for example, `pnminterp 2' produces\n\
		a picture with twice the width and twice the height.\n");
exit(0);
}



static void interp(FILE *in,const int cols,const int rows, 
                   const xelval maxval,const int format,
                   const int output_format,
                   const int scale,const int edge_mode) 
{
  xel *linebuf1, *linebuf2;  /* Input buffers for two ros at a time */
  xel *curline, *nextline;   /* Pointers to one of the two above buffers */
  xel *outbuf;   /* One-row output buffer */
  int row;

  linebuf1=pnm_allocrow(cols);
  linebuf2=pnm_allocrow(cols);
  outbuf=pnm_allocrow(cols*scale);

  curline=linebuf1;
  nextline=linebuf2;
  pnm_readpnmrow(in,curline,cols,maxval,format);

  for(row=1;row<=rows;row++)
  {
    if(row==rows)
      {
      /* last line is about to be output. there is no further `next line'.
       * if EDGE_DROP, we stop here, with output of rows-1 rows.
       * if EDGE_INTERP_TO_BLACK we make next line black.
       * if EDGE_NON_INTERP (default) we make it a copy of the current line.
       */
      if(edge_mode==EDGE_DROP)
        break;
      
      if(edge_mode==EDGE_INTERP_TO_BLACK) 
        {
        int col;
        for (col=0; col < cols; col++)
          PNM_ASSIGN1(nextline[col], 0);
        } 
      else 
        { /* EDGE_NON_INTERP */
        int col;
        for (col=0; col < cols; col++)
          nextline[col] = curline[col];
        }
      }
    else
      {
      pnm_readpnmrow(in,nextline,cols,maxval,format);
      }
    
    /* interpolate curline towards nextline into outbuf */
    write_interp_line(curline,nextline,outbuf,cols,maxval,format,
                      scale,edge_mode);
    {
      /* Advance "next" line to "current" line by switching line buffers */
      xel *tmp;
      tmp=curline;
      curline=nextline;
      nextline=tmp;
    }
  }
  pnm_freerow(outbuf);
  pnm_freerow(linebuf2);
  pnm_freerow(linebuf1);
}



void write_interp_line(const xel * const curline,
                       const xel * const nextline, 
                       xel * const outbuf,
                       const int cols,const xelval maxval,const int format,
                       const int scale,const int edge_mode)
{
int row;
int a1,a2,a3,a4;
int scaleincr=0,subpix_xpos,subpix_ypos,sxmulsiz,symulsiz,simulsiz=0;
int sisize=0,sis2=0;
int swidth=cols*scale;

if(edge_mode==EDGE_DROP) swidth-=scale;

sisize=0;
while(sisize<256) sisize+=scale;
scaleincr=sisize/scale;
simulsiz=scaleincr*sisize;
sis2=sisize*sisize;

for(row=0;row<scale;row++)
  {
  int col, outcol;

  col=0;
  
  subpix_ypos=(row%scale)*scaleincr;
  subpix_xpos=0;
  
  symulsiz=sisize*subpix_ypos;
  sxmulsiz=sisize*subpix_xpos;
  
  for(outcol=0;outcol<swidth;outcol++)
    {
    a3=symulsiz-(a4=subpix_xpos*subpix_ypos);
    a2=sxmulsiz-a4;
    a1=sis2-sxmulsiz-symulsiz+a4;

    if (col >= cols-1) 
      {
        /* We're at the edge -- nothing to the right of us. */
          if(edge_mode==EDGE_INTERP_TO_BLACK)
              /* assume column to the right of us is black. */
              PPM_ASSIGN(outbuf[outcol], 
                         (PPM_GETR(curline[col])*a1+
                          PPM_GETR(nextline[col])*a3)/sis2,
                         (PPM_GETG(curline[col])*a1+
                          PPM_GETG(nextline[col])*a3)/sis2,
                         (PPM_GETB(curline[col])*a1+
                          PPM_GETB(nextline[col])*a3)/sis2
                  );
          else	/* EDGE_NON_INTERP */
              /* assume column to the right of us is the same as this one */
              PPM_ASSIGN(outbuf[outcol], 
                         (PPM_GETR(curline[col])*(a1+a2)+
                          PPM_GETR(nextline[col])*(a3+a4))/sis2,
                         (PPM_GETG(curline[col])*(a1+a2)+
                          PPM_GETG(nextline[col])*(a3+a4))/sis2,
                         (PPM_GETB(curline[col])*(a1+a2)+
                          PPM_GETB(nextline[col])*(a3+a4))/sis2
                  );
      } else {
          /* Use the column to the right (col+1) in the interpolation */

          PPM_ASSIGN(outbuf[outcol], 
                     (PPM_GETR(curline[col])*a1+
                      PPM_GETR(curline[col+1])*a2+
                      PPM_GETR(nextline[col])*a3+
                      PPM_GETR(nextline[col+1])*a4)/sis2,
                     (PPM_GETG(curline[col])*a1+
                      PPM_GETG(curline[col+1])*a2+
                      PPM_GETG(nextline[col])*a3+
                      PPM_GETG(nextline[col+1])*a4)/sis2,
                     (PPM_GETB(curline[col])*a1+
                      PPM_GETB(curline[col+1])*a2+
                      PPM_GETB(nextline[col])*a3+
                      PPM_GETB(nextline[col+1])*a4)/sis2
              );
      }
    subpix_xpos+=scaleincr;
    sxmulsiz+=simulsiz;
    if(subpix_xpos>=sisize)
      {
      subpix_xpos=sxmulsiz=0;
      col++;
      }
    }
  pnm_writepnmrow(stdout,outbuf,swidth,maxval,format,0);
  }
}
