/* pnmtopng.c - read a portable anymap and produce Public Networks Graphic file
**
** derived from pnmtorast.c (c) 1990,1991 by Jef Poskanzer
**
** Copyright (C) 1995 by Alexander Lehmann <alex@hal.rhein-main.de>
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include "pnm.h"
#include "png.h"

#include "ppmcmap.h"
#define MAXCOLORS 256

static int verbose = 0;

int
main( argc, argv )
     int argc;
     char* argv[];
{
  FILE* ifp;
  xel** xels;
  xel p;
  colorhist_vector chv;
  colorhash_table cht;
  int argn, rows, cols, format, i;
  int depth, colors;
  xelval maxval;
  int interlace, downscale;
  double scaleval;
  int x,y;
  int gray;
  int mayscale;
  png_struct *png_ptr = malloc(sizeof (png_struct));
  png_info *info_ptr = malloc(sizeof (png_info));
  png_color palette[MAXCOLORS];
  png_byte *line;
  png_byte *pp;
  int pass;
  int color;
  char* usage = "[-interlace] [-downscale] [pnmfile]";

  pnm_init( &argc, argv );

  argn = 1;
  interlace=0;
  downscale=0;

  while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
      if ( pm_keymatch( argv[argn], "-verbose", 2 ) )
        verbose=1;
      else
      if ( pm_keymatch( argv[argn], "-interlace", 2 ) )
        interlace=1;
      else
      if ( pm_keymatch( argv[argn], "-downscale", 2 ) )
        downscale=1;
      else
            pm_usage( usage );
      ++argn;
    }

  if ( argn != argc )
    {
      ifp = pm_openr( argv[argn] );
      ++argn;
    }
  else
    ifp = stdin;

  if ( argn != argc )
    pm_usage( usage );

  xels = pnm_readpnm( ifp, &cols, &rows, &maxval, &format );

  pm_close( ifp );

  /* first of all, check if we have a grayscale written as PPM */

  gray=1;
  if(PNM_FORMAT_TYPE(format)==PPM_TYPE) {
    for(x=0;x<cols;x++)
      for(y=0;y<rows;y++) {
        p=xels[y][x];
        if(PPM_GETR(p)!=PPM_GETG(p) || PPM_GETG(p)!=PPM_GETB(p)) {
          gray=0;
          goto break2a;
        }
      }
break2a:
    if(gray) format=PGM_TYPE;
  }

  /* handle `odd' maxvalues */

  if(PNM_FORMAT_TYPE(format)!=PBM_TYPE) {
    if(maxval>65535 && !downscale)
      pm_error("can only handle files up to 16 bit (use -downscale to override");

    if(maxval!=255 && maxval!=65535 &&
       (PNM_FORMAT_TYPE(format)!=PGM_TYPE || (maxval!=1 && maxval!=3 &&
        maxval!=15))) {
      if(maxval<255) {
        pm_message("rescaling to 8 bit");
        scaleval=255.0;
      } else {
        pm_message("rescaling to 16 bit");
        scaleval=65535.0;
      }
      for(y=0;y<rows;y++)
        for(x=0;x<cols;x++) {
          PPM_DEPTH(p, xels[y][x], maxval, scaleval);
          xels[y][x]=p;
        }
      maxval=scaleval;
    }
  }

  /* check for 16 bit entries which are just scaled 8 bit entries, e.g.
     when converting a 8 bit palette TIFF to ppm */

  if(PNM_FORMAT_TYPE(format)!=PBM_TYPE && maxval==65535) {
    mayscale=1;
    for(y=0;y<rows;y++)
      for(x=0;x<cols;x++) {
        p=xels[y][x];
        if(PNM_FORMAT_TYPE(format)==PGM_TYPE ?
           (PNM_GET1(p)&0xff)*0x101!=PNM_GET1(p) :
           (PPM_GETR(p)&0xff)*0x101!=PPM_GETR(p) ||
           (PPM_GETG(p)&0xff)*0x101!=PPM_GETG(p) ||
           (PPM_GETB(p)&0xff)*0x101!=PPM_GETB(p)) {
          mayscale=0;
          goto break2b;
        }
      }
break2b:
    if(mayscale) {
      pm_message("scaling to 8 bit (superflous 16 bit data)");
      for(y=0;y<rows;y++)
        for(x=0;x<cols;x++) {
          p=xels[y][x];
          if(PNM_FORMAT_TYPE(format)==PGM_TYPE) {
            PNM_ASSIGN1(xels[y][x], PNM_GET1(p)&0xff);
          } else {
            PPM_ASSIGN(xels[y][x], PPM_GETR(p)&0xff,  PPM_GETG(p)&0xff,
                       PPM_GETB(p)&0xff);
          }
        }
      maxval=255;
    }
  }

  /* now the same thing for bit depth 4, 2 and 1, only for grayscale pics */

  if(PNM_FORMAT_TYPE(format)==PGM_TYPE && maxval==255) {
      mayscale=1;
      for(y=0;y<rows;y++)
        for(x=0;x<cols;x++) {
          if((PNM_GET1(xels[y][x])&0xf)*0x11!=PNM_GET1(xels[y][x]))
            mayscale=0;
          goto break2c;
        }
break2c:
      if(mayscale) {
        for(y=0;y<rows;y++)
          for(x=0;x<cols;x++) {
            PNM_ASSIGN1(xels[y][x], PNM_GET1(xels[y][x])&0xf);
          }
        maxval=15;
      }
    }

  if(PNM_FORMAT_TYPE(format)==PGM_TYPE && maxval==15) {
    mayscale=1;
    for(y=0;y<rows;y++)
      for(x=0;x<cols;x++) {
        if((PNM_GET1(xels[y][x])&3)*5!=PNM_GET1(xels[y][x]))
          mayscale=0;
        goto break2d;
      }
break2d:
    if(mayscale) {
      for(y=0;y<rows;y++)
        for(x=0;x<cols;x++) {
          PNM_ASSIGN1(xels[y][x], PNM_GET1(xels[y][x])&3);
        }
      maxval=3;
    }
  }

  if(PNM_FORMAT_TYPE(format)==PGM_TYPE && maxval==3) {
    mayscale=1;
    for(y=0;y<rows;y++)
      for(x=0;x<cols;x++) {
        if((PNM_GET1(xels[y][x])&1)*3!=PNM_GET1(xels[y][x]))
          mayscale=0;
        goto break2e;
      }
break2e:
    if(mayscale) {
      for(y=0;y<rows;y++)
        for(x=0;x<cols;x++) {
          PNM_ASSIGN1(xels[y][x], PNM_GET1(xels[y][x])&1);
        }
      maxval=1;
    }
  }

  /* Figure out the proper depth and colormap. */
  switch ( PNM_FORMAT_TYPE(format) ) {
  case PPM_TYPE:
    if(maxval==255) {
      pm_message( "computing colormap..." );
      chv = ppm_computecolorhist( xels, cols, rows, MAXCOLORS, &colors );
      if ( chv == (colorhist_vector) 0 ) {
        pm_message("Too many colors - proceeding to write a 24-bit non-mapped" );
        pm_message("image file.  If you want 8 bits, try doing a 'ppmquant %d'.",
                   MAXCOLORS );
        depth = 8;
      } else {
        pm_message( "%d colors found", colors );

        depth=8;
        if(colors<=16) depth=4;
        if(colors<=4) depth=2;
        if(colors<=2) depth=1;

      }
    } else {
      depth=16;
    }

    break;

  case PGM_TYPE:
    if(maxval==65535)
      depth=16;
    else if(maxval==255)
      depth=8;
    else if(maxval==15)
      depth=4;
    else if(maxval==3)
      depth=2;
    else if(maxval==1)
      depth=1;
    else
      pm_error("(can't happen) undefined maxvalue");
    break;

  default:
    depth = 1;
    break;
  }

  if(verbose)
    pm_message("writing a %d bit %s file%s", depth,
      PNM_FORMAT_TYPE(format)!=PPM_TYPE?"gray":(chv!=NULL?"palette":"rbg"),
      interlace ? "(interlaced)" : "");

  /* now write the file */

  if(!setjmp(png_ptr->jmpbuf)) {
    png_write_init(png_ptr);
    png_info_init(info_ptr);
    png_init_io(png_ptr, stdout);
    info_ptr->width=cols;
    info_ptr->height=rows;
    info_ptr->bit_depth=depth;

    if(PNM_FORMAT_TYPE(format)==PPM_TYPE) {
      info_ptr->color_type=chv!=NULL ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_RGB;
    } else {
      info_ptr->color_type=PNG_COLOR_TYPE_GRAY;
    }
    info_ptr->interlace_type=interlace;

    if(info_ptr->color_type==PNG_COLOR_TYPE_PALETTE) {
      for(i=0;i<MAXCOLORS;i++) {
        palette[i].red=PPM_GETR(chv[i].color);
        palette[i].green=PPM_GETG(chv[i].color);
        palette[i].blue=PPM_GETB(chv[i].color);
      }
      info_ptr->palette=palette;
      info_ptr->num_palette=colors;
      info_ptr->valid |= PNG_INFO_PLTE;

      cht = ppm_colorhisttocolorhash( chv, colors );
      ppm_freecolorhist( chv );
    }

    png_write_info(png_ptr, info_ptr);

    png_set_packing(png_ptr);

    line=malloc(cols*6);

    for(pass=0;pass<png_set_interlace_handling(png_ptr);pass++) {
      for(y=0;y<rows;y++) {
        pp=line;
        for(x=0;x<cols;x++) {
          p=xels[y][x];
          if(info_ptr->color_type==PNG_COLOR_TYPE_GRAY) {
            if(depth==16) {
              *pp++=PNM_GET1(p)>>8;
            }
            *pp++=PNM_GET1(p)&0xff;
          } else if(info_ptr->color_type==PNG_COLOR_TYPE_PALETTE) {
            color = ppm_lookupcolor( cht, &p);
            *pp++=color;
          } else {
            if(depth==16) {
              *pp++=PPM_GETR(p)>>8;
            }
            *pp++=PPM_GETR(p)&0xff;
            if(depth==16) {
              *pp++=PPM_GETG(p)>>8;
            }
            *pp++=PPM_GETG(p)&0xff;
            if(depth==16) {
              *pp++=PPM_GETB(p)>>8;
            }
            *pp++=PPM_GETB(p)&0xff;
          }
        }
        png_write_row(png_ptr, line);
      }
    }

    png_write_end(png_ptr, info_ptr);
    png_write_destroy(png_ptr);

    free(png_ptr);
    free(info_ptr);

  } else {
    pm_error("setjmp returns error condition");
    free(png_ptr);
    free(info_ptr);
    exit(1);
  }
  exit( 0 );
}
