/*
** pngtopnm.c - read a Portable Network Graphics file and produce a portable
** anymap
**
** Copyright (C) 1995 Alexander Lehmann
**
** 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.
**
** modeled after giftopnm by David Koblas and
** with lots of bits pasted from pnglib.txt by Guy Eric Schalnat
*/

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

#define TRUE 1
#define FALSE 0

enum alpha_handling
{ none, alpha_only, mix };

/* prototypes */
static void store_pixel ARGS((xel *pix, png_uint_16 r, png_uint_16 g, png_uint_16 b, png_uint_16 a));
static png_uint_16 _get_png_val ARGS((png_byte **pp, int bit_depth));
static void print_text ARGS((png_info *info_ptr));
static void convertPNG ARGS((FILE *in));

static int verbose=FALSE;
static int text=FALSE;

static enum alpha_handling alpha=none;

static pixval maxval;

#define get_png_val(p) _get_png_val(&(p),info_ptr->bit_depth)

static
#ifdef __STDC__
png_uint_16 _get_png_val(png_byte **pp, int bit_depth)
#else
png_uint_16 _get_png_val(pp, bit_depth)
png_byte **pp;
int bit_depth;
#endif
{
  png_uint_16 c=0;

  if(bit_depth==16) {
    c=(*((*pp)++))<<8;
  }
  c|=(*((*pp)++));

  return c;
}

static
#ifdef __STD__
void store_pixel(xel *pix, png_uint_16 r, png_uint_16 g, png_uint_16 b, png_uint_16 a)
#else
void store_pixel(pix, r, g, b, a)
xel *pix;
png_uint_16 r, g, b, a;
#endif
{
  if(alpha==alpha_only) {
    PNM_ASSIGN1(*pix, a);
  } else
  if(alpha==mix) {
    r*=(double)a/maxval;
    g*=(double)a/maxval;
    b*=(double)a/maxval;
    PPM_ASSIGN(*pix, r, g, b);
  } else {
    PPM_ASSIGN(*pix, r, g, b);
  }
}

static
#ifdef __STDC__
void print_text(png_info *info_ptr)
#else
void print_text(info_ptr)
png_info *info_ptr;
#endif
{
  int i;
  static char *month[]=
    {"January", "Feburary", "March", "April", "May", "June", "July", "August",
       "September", "October", "November", "December"};

  for(i=0;i<info_ptr->num_text;i++) {
    pm_message("\n%s:%.*s", info_ptr->text[i].key,
               (int)info_ptr->text[i].text_length,
               info_ptr->text[i].text);
  }

  if(info_ptr->valid & PNG_INFO_tIME) {
    pm_message("modification time: %02d %s %d %02d:%02d:%02d",
               info_ptr->mod_time.day, month[info_ptr->mod_time.month],
               info_ptr->mod_time.year, info_ptr->mod_time.hour,
               info_ptr->mod_time.minute, info_ptr->mod_time.second);
  }
}

static
#ifdef __STDC__
void convertPNG(FILE *in)
#else
void convertPNG(in)
FILE *in;
#endif
{
  png_struct *png_ptr = malloc(sizeof (png_struct));
  png_info *info_ptr = malloc(sizeof (png_info));
  pixel *row;
  png_byte **png_image;
  png_byte *png_pixel;
  pixel *pnm_pixel;
  int x,y;
  int linesize;
  png_uint_16 c,c2,c3,a;
  int pnm_type;
  int i;
  char *type_string;
  char *alpha_string;

  if(png_ptr==NULL || info_ptr==NULL)
    pm_error("Cannot allocate PNGLIB structures");

  if(setjmp(png_ptr->jmpbuf)==0) {
    png_read_init(png_ptr);
    png_info_init(info_ptr);
    png_init_io(png_ptr, in);
    png_read_info(png_ptr, info_ptr);

    if(verbose) {
      switch(info_ptr->color_type) {
        case PNG_COLOR_TYPE_GRAY:
          type_string="gray";
          alpha_string="";
          break;

        case PNG_COLOR_TYPE_GRAY_ALPHA:
          type_string="gray";
          alpha_string="+alpha";
          break;

        case PNG_COLOR_TYPE_PALETTE:
          type_string="palette";
          alpha_string="";
          break;

        case PNG_COLOR_TYPE_RGB:
          type_string="truecolor";
          alpha_string="";
          break;

        case PNG_COLOR_TYPE_RGB_ALPHA:
          type_string="truecolor";
          alpha_string="+alpha";
          break;
      }
      if(info_ptr->valid & PNG_INFO_tRNS) {
        alpha_string="+transparency";
      }

      pm_message("reading a %d x %d image, %d bit%s %s%s%s",
                 info_ptr->width, info_ptr->height,
                 info_ptr->bit_depth, info_ptr->bit_depth>1 ? "s" : "",
                 type_string, alpha_string,
                 info_ptr->interlace_type ? " Adam7 interlaced" : "");
    }

    png_image=malloc(info_ptr->height*sizeof(png_byte*));
    if(png_image==NULL)
      pm_error("couldn't alloc space for image");

    if(info_ptr->bit_depth==16)
      linesize=2*info_ptr->width;
    else
      linesize=info_ptr->width;

    if(info_ptr->color_type==PNG_COLOR_TYPE_GRAY_ALPHA)
      linesize*=2;
    else
    if(info_ptr->color_type==PNG_COLOR_TYPE_RGB)
      linesize*=3;
    else
    if(info_ptr->color_type==PNG_COLOR_TYPE_RGB_ALPHA)
      linesize*=4;

    for(y=0;y<info_ptr->height;y++) {
      png_image[y]=malloc(linesize);
      if(png_image[y]==NULL)
        pm_error("couldn't alloc space for image");
    }

    if (info_ptr->bit_depth < 8)
      png_set_packing(png_ptr);

    png_read_image(png_ptr, png_image);
    png_read_end(png_ptr, info_ptr);

    if(text) {
      print_text(info_ptr);
    }

    if(info_ptr->valid & PNG_INFO_pHYs) {
      float r;
      r=(float) info_ptr->x_pixels_per_unit / info_ptr->y_pixels_per_unit;
      if(r!=1.0) {
        pm_message("warning - non-square pixels; to fix do a 'pnmscale -%cscale %g'",
                   r < 1.0 ? 'x' : 'y',
                   r < 1.0 ? 1.0 / r : r );
      }
    }

    if ((row = pnm_allocrow(info_ptr->width)) == NULL)
      pm_error("couldn't alloc space for image");

    if(info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
      maxval=255;
    } else {
      maxval=(1l<<info_ptr->bit_depth)-1;
    }

    if(alpha==alpha_only) {
      if(info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
         info_ptr->color_type == PNG_COLOR_TYPE_RGB) {
        pnm_type=PBM_TYPE;
      } else
        if(info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) {
          pnm_type=PBM_TYPE;
          if(info_ptr->valid & PNG_INFO_tRNS) {
            for(i=0;i<info_ptr->num_trans;i++) {
              if(info_ptr->trans[i]!=0 && info_ptr->trans[i]!=maxval) {
                pnm_type=PGM_TYPE;
                break;
              }
            }
          }
        } else {
          pnm_type=PGM_TYPE;
        }
    } else {
      if(info_ptr->color_type == PNG_COLOR_TYPE_GRAY ||
         info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
        if(info_ptr->bit_depth == 1) {
          pnm_type=PBM_TYPE;
        } else {
          pnm_type=PGM_TYPE;
        }
      } else {
        pnm_type=PPM_TYPE;
      }
    }

    if (verbose)
      pm_message("writing a %s file",
                 pnm_type == PBM_TYPE ? "PBM" :
                 pnm_type == PGM_TYPE ? "PGM" :
                 pnm_type == PPM_TYPE ? "PPM" :
                                                "UNKNOWN!");

    pnm_writepnminit(stdout, info_ptr->width, info_ptr->height, maxval,
                     pnm_type, FALSE);

    for(y=0;y<info_ptr->height;y++) {
      png_pixel=png_image[y];
      pnm_pixel=row;
      for(x=0;x<info_ptr->width;x++) {
        c=get_png_val(png_pixel);
        switch(info_ptr->color_type) {
          case PNG_COLOR_TYPE_GRAY:
            store_pixel(pnm_pixel, c, c, c,
                        (info_ptr->valid & PNG_INFO_tRNS) &&
                         c==info_ptr->trans_values.gray ?
                         0 : maxval);
            break;

          case PNG_COLOR_TYPE_GRAY_ALPHA:
            a=get_png_val(png_pixel);
            store_pixel(pnm_pixel, c, c, c, a);
            break;

          case PNG_COLOR_TYPE_PALETTE:
            store_pixel(pnm_pixel, info_ptr->palette[c].red,
                        info_ptr->palette[c].green, info_ptr->palette[c].blue,
                        (info_ptr->valid & PNG_INFO_tRNS) &&
                         c<info_ptr->num_trans ?
                         info_ptr->trans[c] : maxval);
            break;

          case PNG_COLOR_TYPE_RGB:
            c2=get_png_val(png_pixel);
            c3=get_png_val(png_pixel);
            store_pixel(pnm_pixel, c, c2, c3,
                        (info_ptr->valid & PNG_INFO_tRNS) &&
                         c ==info_ptr->trans_values.red &&
                         c2==info_ptr->trans_values.green &&
                         c3==info_ptr->trans_values.blue ?
                         0 : maxval);
            break;

          case PNG_COLOR_TYPE_RGB_ALPHA:
            c2=get_png_val(png_pixel);
            c3=get_png_val(png_pixel);
            a =get_png_val(png_pixel);
            store_pixel(pnm_pixel, c, c2, c3, a);
            break;

          default:
            pm_error("unknown PNG color type");
        }
        pnm_pixel++;
      }
      pnm_writepnmrow(stdout, row, info_ptr->width, maxval, pnm_type, FALSE);
    }
    png_read_destroy(png_ptr, info_ptr, (png_info *)NULL);

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

#ifdef __STDC__
int main(int argc, char *argv[])
#else
int main(argc, argv)
int argc;
char *argv[];
#endif
{
  FILE *in;
  int argn;
  char *usage = "[-verbose] [-text] [-alpha|-mix] [pngfile]";

  pnm_init(&argc, argv);

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

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

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

  convertPNG(in);
  pm_close(in);
  pm_close(stdout);
  exit(0);
}

