/*****************************************************************************
 *
 * MODULE:       r.out.png
 * AUTHOR(S):    Bill Brown - USA-CERL
 *               Alex Shevlakov - sixote@yahoo.com
 *               Hamish Bowman
 * PURPOSE:      Export GRASS raster as non-georeferenced PNG image.
 * COPYRIGHT:    (C) 2000-2010 by the GRASS Development Team
 *
 *               This program is free software under the GNU General Public
 *               License (>=v2). Read the file COPYING that comes with GRASS
 *               for details.
 *
 *****************************************************************************/

/*
 * Alex Shevlakov, sixote@yahoo.com, 03/2000
 * based on r.out.ppm by
 * Written by Bill Brown, USA-CERL March 21, 1994
 *
 * Use to convert grass raster map to PNG
 * uses currently selected region
 *
 */

#include <string.h>
#include <stdlib.h>
#include <float.h>

#ifndef _MYINCLUDE_H
#define _MYINCLUDE_H
#include <png.h>
#include "pngfunc.h"
/* #include <pnm.h> this is already included from pngfunc.h */
#endif /* _MYINCLUDE_H */

#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/colors.h>
#include <grass/glocale.h>

typedef int FILEDESC;

/* global functions */
static int write_wld(const char *, const struct Cell_head *);

int main(int argc, char *argv[])
{
    struct GModule *module;
    struct Option *rast, *png_file, *compr; /* , *bgcolor; */
    struct Flag *alpha, *wld_flag;

    /* char *rastermap; */
    char *basename = NULL, *outfile = NULL;
    unsigned char *set, *ored, *ogrn, *oblu;
    int def_red, def_grn, def_blu;
    CELL *cell_buf;
    FCELL *fcell_buf;
    DCELL *dcell_buf;
    void *voidc;
    int rtype, row, col, do_stdout = 0;
    size_t rsize;
    int png_compr, /* ret, */ do_alpha;
    struct Cell_head win;
    FILEDESC cellfile = 0;
    FILE *fp;

    /* now goes from pnmtopng.c* -A.Sh */
    /*
     * pnmtopng.c -
     * read a portable anymap and produce a Portable Network Graphics file
     *
     * derived from pnmtorast.c (c) 1990,1991 by Jef Poskanzer and some
     * parts derived from ppmtogif.c by Marcel Wijkstra <wijkstra@fwi.uva.nl>
     * thanks to Greg Roelofs <newt@pobox.com> for contributions and bug-fixes
     *
     * Copyright (C) 1995-1998 by Alexander Lehmann <alex@hal.rhein-main.de>
     *                        and Willem van Schaik <willem@schaik.com>
     *
     * 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.
     */

    png_struct *png_ptr;
    png_info *info_ptr;

    png_byte *line;
    png_byte *pp;

    /* these variables are declared static because gcc wasn't kidding
     * about "variable XXX might be clobbered by `longjmp' or `vfork'"
     * (stack corruption observed on Solaris 2.6 with gcc 2.8.1, even
     * in the absence of any other error condition) */
    /* static xelval maxmaxval; */

    static int depth;
    static int filter;

    /* these guys are initialized to quiet compiler warnings: */
    /* maxmaxval = 255; */
    depth = 0;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("raster"));
    G_add_keyword(_("export"));
    G_add_keyword(_("output"));
    G_add_keyword("PNG");
    module->description =
        _("Export a GRASS raster map as a non-georeferenced PNG image.");

    rast = G_define_standard_option(G_OPT_R_INPUT);

    png_file = G_define_standard_option(G_OPT_F_OUTPUT);
    png_file->required = YES;
    png_file->description = _("Name for new PNG file (use '-' for stdout)");

    compr = G_define_option();
    compr->key = "compression";
    compr->type = TYPE_INTEGER;
    compr->required = NO;
    compr->multiple = NO;
    compr->options = "0-9";
    compr->label = _("Compression level of PNG file");
    compr->description = _("(0 = none, 1 = fastest, 9 = best)");
    compr->answer = "6";

    /*    bgcolor = G_define_standard_option(G_OPT_C_BG); */

    alpha = G_define_flag();
    alpha->key = 't';
    alpha->description = _("Make NULL cells transparent");

    wld_flag = G_define_flag();
    wld_flag->key = 'w';
    wld_flag->description = _("Output world file");

    /* see what can be done to convert'em -A.Sh.
     * gscale = G_define_flag ();
     * gscale->key = 'g';
     * gscale->description = "Output greyscale instead of color";
     */

    if (G_parser(argc, argv))
        exit(EXIT_FAILURE);

    /* rastermap = rast->answer; */

    do_alpha = alpha->answer ? TRUE : FALSE;

    if (strcmp(png_file->answer, "-") != 0)
        basename = G_store(png_file->answer);
    else
        do_stdout = TRUE;

    if (basename) {
        G_basename(basename, "png");
        outfile = G_malloc(strlen(basename) + 5);
        sprintf(outfile, "%s.png", basename);
    }

    png_compr = atoi(compr->answer);

#ifdef MAYBE_LATER
    /* ... if at all */
    int ret;
    ret = G_str_to_color(bgcolor->answer, &def_red, &def_grn, &def_blu);
    if (ret == 0)
        G_fatal_error(_("[%s]: No such color"), bgcolor->answer);
    else if (ret == 2) { /* (ret==2) is "none" */
        if (!do_alpha)
            do_alpha = TRUE;
    }
#else
    G_str_to_color(DEFAULT_BG_COLOR, &def_red, &def_grn, &def_blu);
#endif

    /*G_get_set_window (&win); */ /* 10/99 MN: check for current region */
    G_get_window(&win);

    G_debug(1, "rows = %d, cols = %d", win.rows, win.cols);

    /* open raster map for reading */
    cellfile = Rast_open_old(rast->answer, "");

    cell_buf = Rast_allocate_c_buf();
    fcell_buf = Rast_allocate_f_buf();
    dcell_buf = Rast_allocate_d_buf();

    ored = G_malloc(win.cols);
    ogrn = G_malloc(win.cols);
    oblu = G_malloc(win.cols);
    set = G_malloc(win.cols);

    /* open png file for writing */
    if (do_stdout)
        fp = stdout;
    else if (NULL == (fp = fopen(outfile, "w")))
        G_fatal_error(_("Unable to open output file <%s>"), outfile);

    png_ptr =
        png_create_write_struct(PNG_LIBPNG_VER_STRING, &pnmtopng_jmpbuf_struct,
                                pnmtopng_error_handler, NULL);
    if (png_ptr == NULL) {
        fclose(fp);
        G_fatal_error("cannot allocate LIBPNG structure");
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL) {
        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
        fclose(fp);
        G_fatal_error("cannot allocate LIBPNG structure");
    }

    if (setjmp(pnmtopng_jmpbuf_struct.jmpbuf)) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        fclose(fp);
        G_fatal_error("setjmp returns error condition (1)");
    }

    depth = 8; /*really??? */

#ifdef OLDPNG
    png_write_init(png_ptr);
    png_info_init(info_ptr);
#endif
    png_init_io(png_ptr, fp);

    png_set_IHDR(png_ptr, info_ptr, win.cols, win.rows, depth,
                 do_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB,
                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_DEFAULT);

    /* explicit filter-type (or none) required */
    if ((filter >= 0) && (filter <= 4)) {
        png_set_filter(png_ptr, 0, filter);
    }

    png_set_compression_level(png_ptr, png_compr);

    if (do_alpha) {
        png_color_16 background_color;

        background_color.red = (png_uint_16)def_red;
        background_color.green = (png_uint_16)def_grn;
        background_color.blue = (png_uint_16)def_blu;
        png_set_bKGD(png_ptr, info_ptr, &background_color);
    }

    G_verbose_message(_("Converting <%s>..."), rast->answer);

    {
        struct Colors colors;

        Rast_read_colors(rast->answer, "", &colors);

        rtype = Rast_get_map_type(cellfile);
        if (rtype == CELL_TYPE)
            voidc = (CELL *)cell_buf;
        else if (rtype == FCELL_TYPE)
            voidc = (FCELL *)fcell_buf;
        else if (rtype == DCELL_TYPE)
            voidc = (DCELL *)dcell_buf;
        else
            G_fatal_error(_("Raster <%s> type mismatch"), rast->answer);

        rsize = Rast_cell_size(rtype);

        /*if(!gscale->answer){ */ /* 24BIT COLOR IMAGE */

        if (TRUE) {
            /* write the png-info struct */
            png_write_info(png_ptr, info_ptr);

            /* let libpng take care of, e.g., bit-depth conversions */
            png_set_packing(png_ptr);

            /* max: 3 color channels, one alpha channel, 16-bit */
            line = (png_byte *)G_malloc(win.cols * 8 * sizeof(char));

            for (row = 0; row < win.rows; row++) {

                G_percent(row, win.rows, 5);
                Rast_get_row(cellfile, (void *)voidc, row, rtype);
                Rast_lookup_colors((void *)voidc, ored, ogrn, oblu, set,
                                   win.cols, &colors, rtype);

                pp = line;

                for (col = 0; col < win.cols; col++) {

                    if (set[col]) {
                        *pp++ = ored[col];
                        *pp++ = ogrn[col];
                        *pp++ = oblu[col];
                        if (do_alpha) {
                            if (Rast_is_null_value(
                                    G_incr_void_ptr(voidc, col * rsize), rtype))
                                *pp++ = 0;
                            else
                                *pp++ = 255;
                        }
                    }
                    else {
                        if (do_alpha) {
                            *pp++ = ored[col];
                            *pp++ = ogrn[col];
                            *pp++ = oblu[col];
                            *pp++ = 0;
                        }
                        else {
                            *pp++ = (unsigned char)def_red;
                            *pp++ = (unsigned char)def_grn;
                            *pp++ = (unsigned char)def_blu;
                        }
                    }
                }

                png_write_row(png_ptr, line);
            }
            G_percent(row, win.rows, 5); /* finish it off */
        }
        else { /* GREYSCALE IMAGE */

            /*
             * info_ptr->color_type = PNG_COLOR_TYPE_GRAY;
             *
             */

            /* pm_message ("don't know yet how to write grey - yumm!!"); */
            G_warning("don't know how to write grey scale!");
        }

        Rast_free_colors(&colors);
    }

    G_free(cell_buf);
    G_free(fcell_buf);
    G_free(dcell_buf);
    G_free(ored);
    G_free(ogrn);
    G_free(oblu);
    G_free(set);
    Rast_close(cellfile);

    png_write_end(png_ptr, info_ptr);
    /* png_write_destroy (png_ptr); this is no longer supported with libpng, al
     * 11/2000 */
    /* flush first because G_free (png_ptr) can segfault due to jmpbuf problems
     * in png_write_destroy */

    fflush(stdout);
    /* G_free (png_ptr); */
    /* G_free (info_ptr); */
    png_destroy_write_struct(&png_ptr, &info_ptr); /* al 11/2000 */

    fclose(fp);

    if (wld_flag->answer) {
        if (do_stdout)
            outfile = G_store("png_map.wld");
        else
            sprintf(outfile, "%s.wld", basename);

        write_wld(outfile, &win);
    }

    if (basename)
        G_free(basename);
    if (outfile)
        G_free(outfile);

    exit(EXIT_SUCCESS);
}

#ifdef __STDC__
static void pnmtopng_error_handler(png_structp png_ptr, png_const_charp msg)
#else
static void pnmtopng_error_handler(png_ptr, msg) png_structp png_ptr;
png_const_charp msg;
#endif
{
    jmpbuf_wrapper *jmpbuf_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. */

    G_warning("pnmtopng:  fatal libpng error: [%s]", msg);

    jmpbuf_ptr = png_get_error_ptr(png_ptr);
    if (jmpbuf_ptr == NULL) { /* we are completely hosed now */
        G_fatal_error("pnmtopng:  EXTREMELY fatal error: jmpbuf unrecoverable; "
                      "terminating.");
    }

    longjmp(jmpbuf_ptr->jmpbuf, 1);
}

static int write_wld(const char *fname, const struct Cell_head *win)
{
    int width = DBL_DIG;
    FILE *ofile;

    G_verbose_message(_("Writing world file"));

    if (fname == NULL)
        G_fatal_error(_("Got null file name"));
    if (win == NULL)
        G_fatal_error(_("Got null region struct"));
    if ((ofile = fopen(fname, "w")) == NULL)
        G_fatal_error(_("Unable to open world file for writing"));

    fprintf(ofile, "%36.*f \n", width, win->ew_res);
    fprintf(ofile, "%36.*f \n", width, 0.0);
    fprintf(ofile, "%36.*f \n", width, 0.0);
    fprintf(ofile, "%36.*f \n", width, -1 * win->ns_res);
    fprintf(ofile, "%36.*f \n", width, win->west + win->ew_res / 2.0);
    fprintf(ofile, "%36.*f \n", width, win->north - win->ns_res / 2.0);

    fclose(ofile);
    return 0;
}
