/**
 * @file
 * Provide an higher level API to communicate with the libpng, oriented in
 * the gtvirt context.
 */

// ZZZ nettoyer les macros
// ZZZ nettoyer les commentaires

/*****************************************************************************
 *                              Dependencies                                 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <zlib.h>
#include <png.h>  /* libpng header; includes zlib.h and setjmp.h */
#include <inttypes.h>

#include "gsys/cfc.h"

/*****************************************************************************
 *                                Macros                                     *
 *****************************************************************************/

#ifndef TRUE
#  define TRUE 1
#  define FALSE 0
#endif

#ifndef MAX
#  define MAX(a,b)  ((a) > (b)? (a) : (b))
#  define MIN(a,b)  ((a) < (b)? (a) : (b))
#endif

#define TEXT_TITLE    0x01
#define TEXT_AUTHOR   0x02
#define TEXT_DESC     0x04
#define TEXT_COPY     0x08
#define TEXT_EMAIL    0x10
#define TEXT_URL      0x20

#define TEXT_TITLE_OFFSET        0
#define TEXT_AUTHOR_OFFSET      72
#define TEXT_COPY_OFFSET     (2*72)
#define TEXT_EMAIL_OFFSET    (3*72)
#define TEXT_URL_OFFSET      (4*72)
#define TEXT_DESC_OFFSET     (5*72)

typedef unsigned char   uch;
typedef unsigned short  ush;
typedef unsigned long   ulg;

struct mem_encode
{
  char *buffer;
  size_t size;
};

// ZZZ Nettoyer cette structure
typedef struct _mainprog_info {
    double gamma;
    long width;
    long height;
    time_t modtime;
    FILE *outfile;
    void *png_ptr;
    void *info_ptr;
    uch *image_data;
    uch **row_pointers;
    struct mem_encode state;
    char *title;
    char *author;
    char *desc;
    char *copyright;
    char *email;
    char *url;
    int filter;    /* command-line-filter flag, not PNG row filter! */
    int devtype;
    int sample_depth;
    int interlaced;
    int have_bg;
    int have_time;
    int have_text;
    jmp_buf jmpbuf;
    uch bg_red;
    uch bg_green;
    uch bg_blue;
} mainprog_info;


/*****************************************************************************
 *                         Function Declarations                             *
 *****************************************************************************/

/* local prototypes */
static mainprog_info wpng_info;   /* lone global */
static void wpng_cleanup(void);
static void writepng_error_handler(png_structp png_ptr, png_const_charp msg);
static void writepng_version_info(void);
static int  writepng_init(mainprog_info *mainprog_ptr);
static int  writepng_encode_image(mainprog_info *mainprog_ptr);
static int  writepng_encode_row(mainprog_info *mainprog_ptr);
static int  writepng_encode_finish(mainprog_info *mainprog_ptr);
static void writepng_cleanup(mainprog_info *mainprog_ptr);

/* external prototypes */
int   CFC_API  gpng_zopen     (CFC_FzString, int32_t *, int32_t *, int32_t *, int32_t *);
uch   CFC_API  si4_to_ui1     (int *);
void  CFC_API  gpng_copy_data (int32_t *, uch *, uch *, uch *, uch *);
void  CFC_API  gpng_finish    ();
void  CFC_API  gpng_cleanup   ();
void  CFC_API  gpng_getbuf    (int32_t *nwords32, void **addr);

#define gpng_zopen      CFC_EXPORT_NAME(gpng_zopen)
#define si4_to_ui1      CFC_EXPORT_NAME(si4_to_ui1)
#define gpng_copy_data  CFC_EXPORT_NAME(gpng_copy_data)
#define gpng_finish     CFC_EXPORT_NAME(gpng_finish)
#define gpng_cleanup    CFC_EXPORT_NAME(gpng_cleanup)
#define gpng_getbuf     CFC_EXPORT_NAME(gpng_getbuf)


/*****************************************************************************
 *                        External Function Bodies                           *
 *****************************************************************************/

/**
 * Instantiate and open a new PNG device and its associated file.
 *
 * @param[in] filename is the desired file name,
 * @param[in] lf is the length of the file name,
 * @param[in] npixx is the number of pixels in X,
 * @param[in] npixy is the number of pixels in Y,
 * @param[in] nchan is the number of channels (planes) of the PNG device.
 * @param[out] gpng_zopen return error code.
 */
// ZZZ CFC_FString ?
int CFC_API gpng_zopen(CFC_FzString filename, int32_t *lf,
                       int32_t *npixx, int32_t *npixy, int32_t *nchan) {
  char outfile[256];
  char *p;
//  char *textbuf = NULL;  // ZZZ Re-enable textbuf
  int rc;
  double LUT_exponent;                /* just the lookup table */
  double CRT_exponent = 2.2;          /* just the monitor */
  double default_display_exponent;    /* whole display system */
  double default_gamma = 0.0;

  strncpy( outfile, CFC_fz2c_string( filename), *lf);
  outfile[*lf] = '\0';

  wpng_info.outfile = NULL;
  wpng_info.image_data = NULL;
  wpng_info.row_pointers = NULL;
  wpng_info.state.buffer = NULL;
  wpng_info.state.size = 0;
  wpng_info.filter = FALSE;
  /* Interlacing makes "progressive" images. This implies a larger file, and
    * to store the whole image (all channels) in memory (instead of row by row
    * only). Nevertheless it is supported, so do as you wish. */
  wpng_info.interlaced = FALSE;
  wpng_info.have_bg = FALSE;
  wpng_info.have_time = FALSE;
  wpng_info.have_text = 0;
  wpng_info.gamma = 0.0;

  /* First get the default value for our display-system exponent, i.e.,
    * the product of the CRT exponent and the exponent corresponding to
    * the frame-buffer's lookup table (LUT), if any.  If the PNM image
    * looks correct on the user's display system, its file gamma is the
    * inverse of this value.  (Note that this is not an exhaustive list
    * of LUT values--e.g., OpenStep has a lot of weird ones--but it should
    * cover 99% of the current possibilities.  This section must ensure
    * that default_display_exponent is positive.) */
  LUT_exponent = 1.0;   /* assume no LUT:  most PCs */

  /* the defaults above give 1.0, 1.3, 1.5 and 2.2, respectively: */
  default_display_exponent = LUT_exponent * CRT_exponent;


  /* If the user has set the SCREEN_GAMMA environment variable as suggested
    * (somewhat imprecisely) in the libpng documentation, use that; otherwise
    * use the default value we just calculated.  Either way, the user may
    * override this via a command-line option. */

  if ((p = getenv("SCREEN_GAMMA")) != NULL) {
    double exponent = atof(p);
    if (exponent > 0.0)
      default_gamma = 1.0 / exponent;
  }

  if (default_gamma == 0.0)
    default_gamma = 1.0 / default_display_exponent;

  wpng_info.width = *npixx;
  wpng_info.height = *npixy;
  wpng_info.sample_depth = 8;  /* <==> maxval 255 */
  if (outfile[0] && !(wpng_info.outfile = fopen(outfile,"wb"))) {
    fprintf(stderr, "gpng_zopen: can't open output file [%s]\n",outfile);
    return -1;
  }
  wpng_info.filter = TRUE;
  wpng_info.devtype = *nchan;  /* 1: Grey, 1 byte per pixel
                                * 2: Grey+Alpha, 2 bytes per pixel
                                * 3: RGB, 3 bytes per pixels
                                * 4: RGB+Alpha, 4 bytes per pixel */

//   /* Add character fields in the header */
//   if ( (textbuf = (char *)malloc((5 + 9)*75)) != NULL ) {
// 
//     /* Title field */
//     p = textbuf + TEXT_TITLE_OFFSET;
//     strcpy(p,"Ceci est un titre\n");
//     wpng_info.title = p;
//     wpng_info.have_text |= TEXT_TITLE;
// 
//     /* ZZZ autres? */
// 
//     free(textbuf);
// 
//   } /* textbuf */


  /* Allocate libpng stuff, initialize transformations, write pre-IDAT data */
  if ((rc = writepng_init(&wpng_info)) != 0) {
    switch (rc) {
      case 2:
        fprintf(stderr, "gpng_zopen:  libpng initialization problem (longjmp)\n");
        break;
      case 4:
        fprintf(stderr, "gpng_zopen:  insufficient memory\n");
        break;
      case 11:
        fprintf(stderr, "gpng_zopen:  internal logic error (unexpected PNM type)\n");
        break;
      default:
        fprintf(stderr, "gpng_zopen:  unknown writepng_init() error\n");
        break;
    }
  }

  return rc;

} /* gpng_zopen */


/**
 * Convert 4-bytes Signed Integer into a 1-byte Unsigned Integer. Normally
 * called by Fortran which does not know about Unsigned Integers. This implies
 * that Fortran can NOT do any computations (arithmetics) on the returned
 * values. This is just a pattern of bits it can copy and paste from here to
 * there.
 *
 * @param[in] i4 a value as a 4-bytes Signed Integer,
 * @param[out] si4_to_ui1 this value as a 1-byte Unsigned Integer.
 */
uch CFC_API si4_to_ui1 (int *i4) {
  return (uch ) *i4;
}


/**
 * Copy the input row (1 vector per channel, some may be dummies depending
 * on the number of channels of the current PNG device) to the current
 * PNG device.
 *
 * @param[in] jrow the row number to be copied,
 * @param[in] R the red or grey channel row,
 * @param[in] G the green channel row,
 * @param[in] B the blue channel row,
 * @param[in] A the alpha (transparency) channel row,
 * @param[out] void.
 */
void CFC_API gpng_copy_data(int32_t *jrow, uch *R, uch *G, uch *B, uch *A) {
  ulg rowbytes;
  int error = 0, k, tot;

  /* calculate rowbytes on basis of image type; note that this becomes much
    * more complicated if we choose to support PBM type, ASCII PNM types, or
    * 16-bit-per-sample binary data [currently not an official NetPBM type] */

  rowbytes = wpng_info.width * wpng_info.devtype;  /* Number of bytes per row */

  /* read and write the image, either in its entirety (if writing interlaced
    * PNG) or row by row (if non-interlaced) */

  if (wpng_info.interlaced) {  /* INTERLACED MODE */
    ulg image_bytes = rowbytes * wpng_info.height;   /* overflow? */

    if (wpng_info.image_data == NULL)
      wpng_info.image_data = (uch *)malloc(image_bytes);
    if (wpng_info.row_pointers == NULL)
      wpng_info.row_pointers = (uch **)malloc(wpng_info.height*sizeof(uch *));
    if (wpng_info.image_data == NULL || wpng_info.row_pointers == NULL) {
      fprintf(stderr, "gpng_copy_data:  insufficient memory for image data\n");
      writepng_cleanup(&wpng_info);
      wpng_cleanup();
      exit(5);
    }

    /* Copy to image_data */
    tot = (wpng_info.height-*jrow)*rowbytes;  /* Number of bytes already written */
    wpng_info.row_pointers[wpng_info.height-*jrow] = wpng_info.image_data + tot;
    for (k=0; k<wpng_info.width; k++) {
      wpng_info.image_data[tot] = R[k];
      tot++;
      if (wpng_info.devtype >= 3) {
        wpng_info.image_data[tot] = G[k];
        tot++;
        wpng_info.image_data[tot] = B[k];
        tot++;
      }
      if (wpng_info.devtype == 2 || wpng_info.devtype == 4) {
        wpng_info.image_data[tot] = A[k];
        tot++;
      }
    }

    /* If last row, encode the file. Last row in number 1 since Fortran
      * sends the rows in reverse order (needed for non-interlaced mode) */
    if (*jrow == 1) {
      if (writepng_encode_image(&wpng_info) != 0) {
        fprintf(stderr, "gpng_copy_data:  libpng problem (longjmp) while writing image data\n");
        writepng_cleanup(&wpng_info);
        wpng_cleanup();
        exit(2);
      }
    }

  } else {   /* NON-INTERLACED MODE */

    /* not interlaced:  write progressively (row by row) */
    if (wpng_info.image_data == NULL)
      wpng_info.image_data = (uch *)malloc(rowbytes);
    if (wpng_info.image_data == NULL) {
      fprintf(stderr, "gpng_copy_data:  insufficient memory for row data\n");
      writepng_cleanup(&wpng_info);
      wpng_cleanup();
      exit(5);
    }
    error = 0;

    /* Copy to image_data */
    tot = 0;  /* Number of bytes already written */
    for (k=0; k<wpng_info.width; k++) {
      wpng_info.image_data[tot] = R[k];
      tot++;
      if (wpng_info.devtype >= 3) {
        wpng_info.image_data[tot] = G[k];
        tot++;
        wpng_info.image_data[tot] = B[k];
        tot++;
      }
      if (wpng_info.devtype == 2 || wpng_info.devtype == 4) {
        wpng_info.image_data[tot] = A[k];
        tot++;
      }
    }

    /* Write row */
    if (writepng_encode_row(&wpng_info) != 0) {
      fprintf(stderr, "gpng_copy_data:  libpng problem (longjmp) while writing row %d\n",*jrow);
      ++error;
    }

    if (error) {
      writepng_cleanup(&wpng_info);
      wpng_cleanup();
      exit(2);
    }

  } /* End if interlaced/not_interlaced */

} /* gpng_copy_data */


/**
 * Terminate (add the last few bytes at the end of the file) and close the
 * currently opened PNG device.
 *
 * @param[in] nothing,
 * @param[out] void.
 */
void CFC_API gpng_finish() {

  if (writepng_encode_finish(&wpng_info) != 0) {
    fprintf(stderr, "gpng_finish:  error on final libpng call\n");
    writepng_cleanup(&wpng_info);
    wpng_cleanup();
    exit(2);
  }

}

/**
 * Cleanup up the PNG filler and free global buffers.
 *
 * @param[in] nothing,
 * @param[out] void.
 */
void CFC_API gpng_cleanup() {

  if (wpng_info.state.buffer)
    free(wpng_info.state.buffer);

  /* OK, we're done (successfully):  clean up all resources and quit */
  writepng_cleanup(&wpng_info);
  wpng_cleanup();

}

/**
 * Get the memory buffer address and size
 *
 * @param[out] nwords32  size of memory buffer (for PNG saved in memory
 *                       instead of a file)
 * @param[out] addr      the memory buffer address.
 */
void CFC_API gpng_getbuf(int32_t *nwords32, void **addr)
{
    *(char**)addr = wpng_info.state.buffer;
    *nwords32 = wpng_info.state.size;
}

/*****************************************************************************
 *                        Internal function bodies                           *
 *****************************************************************************/

/**
 * Free memory associated to the current PNG device.
 *
 * @param[in] nothing,
 * @param[out] void.
 */
static void wpng_cleanup(void) {

  if (wpng_info.outfile) {
    fclose(wpng_info.outfile);
    wpng_info.outfile = NULL;
  }

  if (wpng_info.image_data) {
    free(wpng_info.image_data);
    wpng_info.image_data = NULL;
  }

  if (wpng_info.row_pointers) {
    free(wpng_info.row_pointers);
    wpng_info.row_pointers = NULL;
  }

}


/*---------------------------------------------------------------------------
     Below this point, the writepng.c routines as they were available on the
   official documentation at this url:
       http://www.libpng.org/pub/png/  (the canonical url)
       http://www.libpng.org/pub/png/pngbook.html (the documentation book)
   The routines have been partly adapted to the GTVIRT purpose, but the
   copyright is reproduced according to its restrictions.
  ---------------------------------------------------------------------------

      Copyright (c) 1998-2007 Greg Roelofs.  All rights reserved.

      This software is provided "as is," without warranty of any kind,
      express or implied.  In no event shall the author or contributors
      be held liable for any damages arising in any way from the use of
      this software.

      The contents of this file are DUAL-LICENSED.  You may modify and/or
      redistribute this software according to the terms of one of the
      following two licenses (at your option):


      LICENSE 1 ("BSD-like with advertising clause"):

      Permission is granted to anyone to use this software for any purpose,
      including commercial applications, and to alter it and redistribute
      it freely, subject to the following restrictions:

      1. Redistributions of source code must retain the above copyright
         notice, disclaimer, and this list of conditions.
      2. Redistributions in binary form must reproduce the above copyright
         notice, disclaimer, and this list of conditions in the documenta-
         tion and/or other materials provided with the distribution.
      3. All advertising materials mentioning features or use of this
         software must display the following acknowledgment:

            This product includes software developed by Greg Roelofs
            and contributors for the book, "PNG: The Definitive Guide,"
            published by O'Reilly and Associates.


      LICENSE 2 (GNU GPL v2 or later):

      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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  ---------------------------------------------------------------------------*/

void
my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
  /* with libpng15 next line causes pointer deference error; use libpng12 */
  struct mem_encode* p=(struct mem_encode*)png_get_io_ptr(png_ptr); /* was png_ptr->io_ptr */
  size_t nsize = p->size + length;

  /* allocate or grow buffer */
  if(p->buffer)
    p->buffer = realloc(p->buffer, nsize);
  else
    p->buffer = malloc(nsize);

  if(!p->buffer)
    png_error(png_ptr, "Write Error");

  /* copy new bytes to end of buffer */
  memcpy(p->buffer + p->size, data, length);
  p->size += length;
}

/* This is optional but included to show how png_set_write_fn() is called */
void
my_png_flush(png_structp png_ptr)
{
}

void writepng_version_info(void)
{
  fprintf(stderr, "   Compiled with libpng %s; using libpng %s.\n",
    PNG_LIBPNG_VER_STRING, png_libpng_ver);
  fprintf(stderr, "   Compiled with zlib %s; using zlib %s.\n",
    ZLIB_VERSION, zlib_version);
}


/* returns 0 for success, 2 for libpng problem, 4 for out of memory, 11 for
 *  unexpected devtype; note that outfile might be stdout */
int writepng_init(mainprog_info *mainprog_ptr)
{
    png_structp  png_ptr;       /* note:  temporary variables! */
    png_infop  info_ptr;
    int color_type, interlace_type;


    /* could also replace libpng warning-handler (final NULL), but no need: */

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, mainprog_ptr,
      writepng_error_handler, NULL);
    if (!png_ptr)
        return 4;   /* out of memory */

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_write_struct(&png_ptr, NULL);
        return 4;   /* out of memory */
    }


    /* setjmp() must be called in every function that calls a PNG-writing
     * libpng function, unless an alternate error handler was installed--
     * but compatible error handlers must either use longjmp() themselves
     * (as in this program) or exit immediately, so here we go: */

    if (setjmp(mainprog_ptr->jmpbuf)) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        return 2;
    }


    /* make sure outfile is (re)opened in BINARY mode */

    if (mainprog_ptr->outfile) {
        png_init_io(png_ptr, mainprog_ptr->outfile);
    } else {
        /* if my_png_flush() is not needed, change the arg to NULL */
        png_set_write_fn(png_ptr, &mainprog_ptr->state, my_png_write_data, my_png_flush);
    }

    /* set the compression levels--in general, always want to leave filtering
     * turned on (except for palette images) and allow all of the filters,
     * which is the default; want 32K zlib window, unless entire image buffer
     * is 16K or smaller (unknown here)--also the default; usually want max
     * compression (NOT the default); and remaining compression flags should
     * be left alone */

    // png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);  /* Too slow */
    png_set_compression_level(png_ptr, Z_DEFAULT_COMPRESSION);

/*
    >> this is default for no filtering; Z_FILTERED is default otherwise:
    png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
    >> these are all defaults:
    png_set_compression_mem_level(png_ptr, 8);
    png_set_compression_window_bits(png_ptr, 15);
    png_set_compression_method(png_ptr, 8);
 */


    /* set the image parameters appropriately */

    if (mainprog_ptr->devtype == 1)
        color_type = PNG_COLOR_TYPE_GRAY;
    else if (mainprog_ptr->devtype == 2)
        color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
    else if (mainprog_ptr->devtype == 3)
        color_type = PNG_COLOR_TYPE_RGB;
    else if (mainprog_ptr->devtype == 4)
        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
    else {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        return 11;
    }

    interlace_type = mainprog_ptr->interlaced? PNG_INTERLACE_ADAM7 :
                                               PNG_INTERLACE_NONE;

    png_set_IHDR(png_ptr, info_ptr, mainprog_ptr->width, mainprog_ptr->height,
      mainprog_ptr->sample_depth, color_type, interlace_type,
      PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

    if (mainprog_ptr->gamma > 0.0)
        png_set_gAMA(png_ptr, info_ptr, mainprog_ptr->gamma);

    if (mainprog_ptr->have_bg) {   /* we know it's RGBA, not gray+alpha */
        png_color_16  background;

        background.red = mainprog_ptr->bg_red;
        background.green = mainprog_ptr->bg_green;
        background.blue = mainprog_ptr->bg_blue;
        png_set_bKGD(png_ptr, info_ptr, &background);
    }

    if (mainprog_ptr->have_time) {
        png_time  modtime;

        png_convert_from_time_t(&modtime, mainprog_ptr->modtime);
        png_set_tIME(png_ptr, info_ptr, &modtime);
    }

    if (mainprog_ptr->have_text) {
        png_text  text[6];
        int  num_text = 0;

        if (mainprog_ptr->have_text & TEXT_TITLE) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Title";
            text[num_text].text = mainprog_ptr->title;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_AUTHOR) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Author";
            text[num_text].text = mainprog_ptr->author;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_DESC) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Description";
            text[num_text].text = mainprog_ptr->desc;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_COPY) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Copyright";
            text[num_text].text = mainprog_ptr->copyright;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_EMAIL) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "E-mail";
            text[num_text].text = mainprog_ptr->email;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_URL) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "URL";
            text[num_text].text = mainprog_ptr->url;
            ++num_text;
        }
        png_set_text(png_ptr, info_ptr, text, num_text);
    }


    /* write all chunks up to (but not including) first IDAT */

    png_write_info(png_ptr, info_ptr);


    /* if we wanted to write any more text info *after* the image data, we
     * would set up text struct(s) here and call png_set_text() again, with
     * just the new data; png_set_tIME() could also go here, but it would
     * have no effect since we already called it above (only one tIME chunk
     * allowed) */


    /* set up the transformations:  for now, just pack low-bit-depth pixels
     * into bytes (one, two or four pixels per byte) */

    png_set_packing(png_ptr);
/*  png_set_shift(png_ptr, &sig_bit);  to scale low-bit-depth values */


    /* make sure we save our pointers for use in writepng_encode_image() */

    mainprog_ptr->png_ptr = png_ptr;
    mainprog_ptr->info_ptr = info_ptr;


    /* OK, that's all we need to do for now; return happy */

    return 0;
}


/* returns 0 for success, 2 for libpng (longjmp) problem */

int writepng_encode_image(mainprog_info *mainprog_ptr)
{
    png_structp png_ptr = (png_structp)mainprog_ptr->png_ptr;
    png_infop info_ptr = (png_infop)mainprog_ptr->info_ptr;


    /* as always, setjmp() must be called in every function that calls a
     * PNG-writing libpng function */

    if (setjmp(mainprog_ptr->jmpbuf)) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        mainprog_ptr->png_ptr = NULL;
        mainprog_ptr->info_ptr = NULL;
        return 2;
    }


    /* and now we just write the whole image; libpng takes care of interlacing
     * for us */

    png_write_image(png_ptr, mainprog_ptr->row_pointers);


    /* since that's it, we also close out the end of the PNG file now--if we
     * had any text or time info to write after the IDATs, second argument
     * would be info_ptr, but we optimize slightly by sending NULL pointer: */

    png_write_end(png_ptr, NULL);

    return 0;
}


/* returns 0 if succeeds, 2 if libpng problem */

int writepng_encode_row(mainprog_info *mainprog_ptr)  /* NON-interlaced only! */
{
    png_structp png_ptr = (png_structp)mainprog_ptr->png_ptr;
    png_infop info_ptr = (png_infop)mainprog_ptr->info_ptr;


    /* as always, setjmp() must be called in every function that calls a
     * PNG-writing libpng function */

    if (setjmp(mainprog_ptr->jmpbuf)) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        mainprog_ptr->png_ptr = NULL;
        mainprog_ptr->info_ptr = NULL;
        return 2;
    }


    /* image_data points at our one row of image data */

    png_write_row(png_ptr, mainprog_ptr->image_data);

    return 0;
}


/* returns 0 if succeeds, 2 if libpng problem */

int writepng_encode_finish(mainprog_info *mainprog_ptr)   /* NON-interlaced! */
{
    png_structp png_ptr = (png_structp)mainprog_ptr->png_ptr;
    png_infop info_ptr = (png_infop)mainprog_ptr->info_ptr;


    /* as always, setjmp() must be called in every function that calls a
     * PNG-writing libpng function */

    if (setjmp(mainprog_ptr->jmpbuf)) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        mainprog_ptr->png_ptr = NULL;
        mainprog_ptr->info_ptr = NULL;
        return 2;
    }


    /* close out PNG file; if we had any text or time info to write after
     * the IDATs, second argument would be info_ptr: */

    png_write_end(png_ptr, NULL);

    return 0;
}


void writepng_cleanup(mainprog_info *mainprog_ptr)
{
    png_structp png_ptr = (png_structp)mainprog_ptr->png_ptr;
    png_infop info_ptr = (png_infop)mainprog_ptr->info_ptr;

    if (png_ptr && info_ptr)
        png_destroy_write_struct(&png_ptr, &info_ptr);
}


static void writepng_error_handler(png_structp png_ptr, png_const_charp msg)
{
    mainprog_info  *mainprog_ptr;

    /* This function, aside from the extra step of retrieving the "error
     * pointer" (below) and the fact that it exists within the application
     * rather than within libpng, is essentially identical to libpng's
     * default error handler.  The second point is critical:  since both
     * setjmp() and longjmp() are called from the same code, they are
     * guaranteed to have compatible notions of how big a jmp_buf is,
     * regardless of whether _BSD_SOURCE or anything else has (or has not)
     * been defined. */

    fprintf(stderr, "writepng libpng error: %s\n", msg);
    fflush(stderr);

    mainprog_ptr = png_get_error_ptr(png_ptr);
    if (mainprog_ptr == NULL) {         /* we are completely hosed now */
        fprintf(stderr,
          "writepng severe error:  jmpbuf not recoverable; terminating.\n");
        fflush(stderr);
        exit(99);
    }

    longjmp(mainprog_ptr->jmpbuf, 1);
}
