The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * $Id: png.trm,v 1.34 2002/08/27 21:22:19 sfeam Exp $
 *
 */

/* GNUPLOT - png.trm */

/*[
 * Copyright 1995, 1998
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or 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.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

/*
 * This file is included by ../term.c.
 *
 * This terminal driver supports:
 *  png
 *
 * AUTHORS
 *  Alexander Lehmann
 *                      derived from pbm.trm by Russell Lang
 *
 *  Eric S. Raymond    update for pnglib-1.0.3; transparency.
 *  Hartmut Freihofer  updates
 *  H.-B. Broeker      fixes to Eric's and Hartmut's changes
 *
 * send your comments or suggestions to (info-gnuplot@dartmouth.edu).
 *
 */

/* To compile this terminal driver, you need libpng and zlib, both are
   available at ftp://ftp.uu.net/graphics/png. Remember to add the
   include dirs and libraries to TERMFLAGS and TERMLIBS. */

/* There is a newer PNG driver that requires libgd version 1.8 or greater.
   If the new driver is used (HAVE_NEWGD) then we should disable this one. */
#ifndef HAVE_NEWGD

/* The following png drivers use the generic bit mapped graphics
   routines from bitmap.c to build up a bit map in memory.  The driver
   interchanges colomns and lines in order to access entire lines
   easily and returns the lines to get bits in the right order :
   (x,y) -> (y,XMAX-1-x). */
/* This interchange is done by calling b_makebitmap() with reversed
   xmax and ymax, and then setting b_rastermode to TRUE.  b_setpixel()
   will then perform the interchange before each pixel is plotted */

#include "driver.h"

#ifdef TERM_REGISTER
register_term(png_driver)
#endif

#ifdef TERM_PROTO
TERM_PUBLIC void PNG_options __PROTO((void));
TERM_PUBLIC void PNG_init __PROTO((void));
TERM_PUBLIC void PNG_reset __PROTO((void));
TERM_PUBLIC void PNG_setfont __PROTO((void));
TERM_PUBLIC void PNG_graphics __PROTO((void));
TERM_PUBLIC void PNG_text __PROTO((void));
TERM_PUBLIC void PNG_linetype __PROTO((int linetype));
TERM_PUBLIC void PNG_point __PROTO((unsigned int x, unsigned int y, int point));
#endif /* TERM_PROTO */

/* make XMAX and YMAX a multiple of 8 */
#define PNG_XMAX (640)
#define PNG_YMAX (480)
#define PNG_VCHAR (FNT5X9_VCHAR)
#define PNG_HCHAR (FNT5X9_VCHAR)
#define PNG_VTIC FNT5X9_HBITS
#define PNG_HTIC FNT5X9_HBITS

#ifdef TERM_BODY

#include "png.h"

/* I'm not sure exactly which is the first version we work with,
 * but I know that some older ones don't define all the symbols
 * we use
 */

/* png version test now in configure */

static int png_font = 1;	/* small font */
static int png_mode = 0;	/* 0:monochrome 1:gray 2:color */
static int png_transparent = 0; /* generate transparent first color */

/* 7=black, 0=white */
static int png_gray[] = { 7, 1, 6, 5, 4, 3, 2, 1, 7 };	/* grays  */
static png_color png_palette[WEB_N_COLORS];

enum PNG_id {
    PNG_TRANSPARENT, PNG_NOTRANSPARENT,
    PNG_SMALL, PNG_MEDIUM, PNG_LARGE,
    PNG_MONOCHROME, PNG_GRAY, PNG_COLOR,
    PNG_PICSIZE,
    PNG_OTHER
};

static struct gen_table PNG_opts[] =
{
    { "t$ransparent", PNG_TRANSPARENT },
    { "not$ransparent", PNG_NOTRANSPARENT },
    { "s$mall", PNG_SMALL },
    { "me$dium", PNG_MEDIUM },
    { "l$arge", PNG_LARGE },
    { "mo$nochrome", PNG_MONOCHROME },
    { "g$ray", PNG_GRAY },
    { "c$olor", PNG_COLOR },
    { "c$olour", PNG_COLOR },
    { "pics$ize", PNG_PICSIZE },
    { NULL, PNG_OTHER }
};

TERM_PUBLIC void
PNG_options()
{
    unsigned short rgb_color[3];
    int n_colors = 0;
    char *string;
    int i;
    int x, y;		/* reading x and y size of picure */
    struct value a;

    rgb_color[0] = 0;
    rgb_color[1] = 0;
    rgb_color[2] = 0;

    png_font = 1; /* small */
    png_mode = 2; /* color */
    png_transparent = 0;       /* use opaque image background */

    term_options[0] = NUL;

    while (!END_OF_COMMAND) {
	
	switch(lookup_table(&PNG_opts[0],c_token)) {

	case PNG_TRANSPARENT:
	    png_transparent = 1;
	    c_token++;
	    break;
	case PNG_NOTRANSPARENT:
	    png_transparent = 0;
	    c_token++;
	    break;
	case PNG_SMALL:
	    png_font = 1;
	    c_token++;
	    break;
	case PNG_MEDIUM:
	    png_font = 2;
	    c_token++;
	    break;
	case PNG_LARGE:
	    png_font = 3;
	    c_token++;
	    break;
	case PNG_MONOCHROME:
	    png_mode = 0;
	    c_token++;
	    break;
	case PNG_GRAY:
	    png_mode = 1;
	    c_token++;
	    break;
	case PNG_COLOR:
	    png_mode = 2;
	    c_token++;
	    break;
	case PNG_PICSIZE:
	    c_token++;
	    x = (int) real(const_express(&a));
	    if (!END_OF_COMMAND) {
	    	y = (int) real(const_express(&a));
		if ((x % 8) != 0) x = 8 * (x / 8);
		term->xmax = x;
		if ((y % 8) != 0) y = 8 * (y / 8);
		term->ymax = y;		/* make x and y multiples of 8 */
	    }
	    break;
	case PNG_OTHER:
	default:
	    /* set color */
	    string = input_line + token[c_token].start_index;

	    if (string[0] == 'x') {
		/* HBB 991123: read in as *shorts*, not ints! */
		if (sscanf(string, "x%2hx%2hx%2hx", &rgb_color[0], &rgb_color[1], &rgb_color[2] ) != 3) {
		    int_error(c_token, "invalid color spec, must be xRRGGBB");
		}
	    } else
		int_error(c_token, "expecting: {small, medium, large},[no]transparent, or {monochrome, gray, color, [xRRGGBB] }");

	    if (n_colors >= WEB_N_COLORS) {
		int_warn(c_token, "too many colors, ingoring");
		/* Magic number abuse guards against
		 * "> WEB_N_COLORS warning" scroll fests. */
		if (!END_OF_COMMAND) {
		    while (!END_OF_COMMAND)
			++c_token;
		    --c_token;
		}
		++c_token;
	    } else {
		web_color_rgbs[n_colors].r = rgb_color[0];
		web_color_rgbs[n_colors].g = rgb_color[1];
		web_color_rgbs[n_colors].b = rgb_color[2];
		n_colors++;
		++c_token;
	    }
	    break;
	}
    }

    /* setup options string */

    /* HBB 991008: moved this block to here, so 'transparent' gets
     * printed out first. Don't print 'notransparent', for now, to
     * protect older gnuplots. Scripts with 'transparent' in them
     * won't work as wanted, with older versions, so put it here. */
    if (png_transparent)
	strcat(term_options, "transparent ");

    switch (png_font) {
    case 3:
	strcat(term_options, "large ");
	break;
    case 2:
	strcat(term_options, "medium ");
	break;
    case 1:
    default:
	strcat(term_options, "small ");
	break;
    }

    switch (png_mode) {
    case 2:
	strcat(term_options, "color ");
	break;
    case 1:
	strcat(term_options, "gray ");
	break;
    case 0:
    default:
	strcat(term_options, "monochrome ");
	break;
    }

    sprintf(term_options + strlen(term_options), "picsize %u %u ",
	    term->xmax, term->ymax);

    for (i = 0; strlen(term_options) + 9 < MAX_LINE_LEN && 
	i < n_colors ; i++ ) {
	sprintf(term_options + strlen(term_options),
		"x%02x%02x%02x ",
		web_color_rgbs[i].r,
		web_color_rgbs[i].g,
		web_color_rgbs[i].b);
    }
}


TERM_PUBLIC void
PNG_init()
{
    PNG_setfont();		/* HBB 980226: do this here! */
}


TERM_PUBLIC void
PNG_reset()
{
#ifdef VMS
    fflush_binary();
#endif /* VMS */
}


TERM_PUBLIC void
PNG_setfont()
{
    switch (png_font) {
    case 3:
	b_charsize(FNT13X25);
	term->v_char = FNT13X25_VCHAR;
	term->h_char = FNT13X25_HCHAR;
	term->v_tic = FNT13X25_HBITS;
	term->h_tic = FNT13X25_HBITS;
	break;
    case 2:
	b_charsize(FNT9X17);
	term->v_char = FNT9X17_VCHAR;
	term->h_char = FNT9X17_HCHAR;
	term->v_tic = FNT9X17_HBITS;
	term->h_tic = FNT9X17_HBITS;
	break;
    case 1:
    default:
	b_charsize(FNT5X9);
	term->v_char = FNT5X9_VCHAR;
	term->h_char = FNT5X9_HCHAR;
	term->v_tic = FNT5X9_HBITS;
	term->h_tic = FNT5X9_HBITS;
	break;
    }
}


TERM_PUBLIC void
PNG_graphics()
{
    int numplanes = 1;

    switch (png_mode) {
    case 2:
	numplanes = 8;
	break;
    case 1:
	numplanes = 3;
	break;
    case 0:
    default:
	numplanes = 1;
	break;
    }

    /* PNGsetfont(); *//* HBB 980226: do this in init() ! */

    /* rotate plot -90 degrees by reversing XMAX and YMAX and by
       setting b_rastermode to TRUE */
    b_makebitmap((unsigned int) (term->ymax * ysize),
		 (unsigned int) (term->xmax * xsize), numplanes);
    b_rastermode = TRUE;

    if (png_mode != 0)
	b_setlinetype(0);	/* solid lines */
}

TERM_PUBLIC void
PNG_text()
{
    png_structp png_ptr;
    png_infop info_ptr;
    png_bytep prow;
    png_text pngtext, *pngtext_copy;
    png_byte pal_trans[1];
    char text[100];
    register int x, i, j;

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
	b_freebitmap();
	return;
    }
    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
	png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
	b_freebitmap();
	return;
    }
    prow = malloc(b_ysize);
    if (!prow) {
	png_destroy_write_struct(&png_ptr, &info_ptr);
	b_freebitmap();
	return;
    }
    memset(prow, 0, b_ysize);
    if (setjmp(png_ptr->jmpbuf)) {
	png_destroy_write_struct(&png_ptr, &info_ptr);
	free(prow);
	b_freebitmap();
	return;
    }

#ifdef __OLD_PNGLIB__
    /* deprecated and no longer necessary */ 
    png_info_init(info_ptr);
    png_write_init(png_ptr); */
#endif /* __OLD_PNGLIB__ */

    png_init_io(png_ptr, gpoutfile);

    info_ptr->width = b_ysize;
    info_ptr->height = b_xsize;

    info_ptr->bit_depth = png_mode == 0 ? 1 : 8;
    info_ptr->color_type = png_mode == 2 ? PNG_COLOR_TYPE_PALETTE :
	PNG_COLOR_TYPE_GRAY;
    if (png_mode == 2) {
	info_ptr->valid |= PNG_INFO_PLTE;
	info_ptr->palette = png_palette;
	info_ptr->num_palette = sizeof(png_palette)/sizeof(png_palette[0]);
    }
    if (png_mode != 0) {
	info_ptr->valid |= PNG_INFO_sBIT;
	if (png_mode == 1) {
	    info_ptr->sig_bit.gray = 3;
	    png_set_shift(png_ptr, &(info_ptr->sig_bit));
	} else {
	    /* HBB 991123: we're now using the full 8 bits per color
	     * component... */
	    info_ptr->sig_bit.red = 8;
	    info_ptr->sig_bit.green = 8;
	    info_ptr->sig_bit.blue = 8;
	}
    }

    info_ptr->interlace_type = 0;

    if (png_mode == 0)
	png_set_invert_mono(png_ptr);

    if (png_mode == 2) {
	if (png_transparent) {
	    info_ptr->valid |= PNG_INFO_tRNS;
	    info_ptr->num_trans = 1;
	    pal_trans[0] = 0;
	    info_ptr->trans=pal_trans;
	}

	for (i = 0; i < WEB_N_COLORS; i++) {
	    png_palette[i].red = web_color_rgbs[i].r;
	    png_palette[i].green = web_color_rgbs[i].g;
	    png_palette[i].blue = web_color_rgbs[i].b;
	}
    }
    sprintf(text, "gnuplot version %s patchlevel %s on %s %s",
	    gnuplot_version, gnuplot_patchlevel, os_name, os_rel);

    pngtext.compression = -1;
    pngtext.key = "Software";
    pngtext.text = text;
    pngtext.text_length = strlen(text);

    pngtext_copy = malloc(sizeof(*pngtext_copy));
    if (!pngtext_copy) {
	png_destroy_write_struct(&png_ptr, &info_ptr);
	free(prow);
	b_freebitmap();
	return;
    }
    /* FIXME HBB 20010824: does this make sense? We're overwriting all
     * its by the struct assignment directly after, anyway! */
    memset(pngtext_copy, 0, sizeof(*pngtext_copy));
    *pngtext_copy = pngtext;
    info_ptr->num_text = 1;
    info_ptr->text = pngtext_copy;

    png_write_info(png_ptr, info_ptr);

    info_ptr->num_text = 0;
    if (info_ptr->text) free(info_ptr->text);
    info_ptr->text = NULL;

    png_set_packing(png_ptr);

    /* dump bitmap in raster mode */
    for (x = b_xsize - 1; x >= 0; x--) {
	for (j=b_ysize - 1; j>= 0; j--)
	    prow[j] = (png_byte) b_getpixel(j, x);

	/* HBB 20010823: This is a bit more straightforward. */
	png_write_row(png_ptr, prow);
    }

    png_write_end(png_ptr, info_ptr);
    png_destroy_write_struct(&png_ptr, &info_ptr);
    free(prow);
    b_freebitmap();
}

/* _linetype(lt)  Called to set the line type before text is displayed or
 * line(s) plotted.  This procedure should select a pen color or line
 * style if the device has these capabilities.  
 * lt is an integer from -2 to 0 or greater.  
 * An lt of -2 is used for the border of the plot.
 * An lt of -1 is used for the X and Y axes.  
 * lt 0 and upwards are used for plots 0 and upwards.
 * If _linetype() is called with lt greater than the available line types, 
 * it should map it to one of the available line types.
 * Most drivers provide 9 different linetypes (lt is 0 to 8).
 */
TERM_PUBLIC void
PNG_linetype(linetype)
int linetype;
{
    switch (png_mode) {
    /* HBB 991008: this once made the grid lines and axes (-1) look the
     * same as the borders (-2). That's ugly, IMHO. GIF uses a
     * dashed line, for this, but libpng doesn't seem able to do
     * that. But a look into the palette turns up that color 2
     * is grey, and is currently unused... Let's see: */
    case 2:

	if (linetype == -2)
	    linetype = 1;
	else if (linetype == -1)
	    linetype = 2;

	else {
	    /* HBB 991008: moved the += 3 down, so colors 0, 1, 2 are
	     * _not_ used by the regular plots, as it should be */
	    if (linetype >= (WEB_N_COLORS - 3))
		linetype %= (WEB_N_COLORS - 3);
	    linetype += 3;
	}
	b_setvalue(linetype);
	break;
    case 1:
	if (linetype >= 7)
	    linetype %= 7;
	b_setvalue(png_gray[linetype + 2]);
	break;
    case 0:
    default:
	b_setlinetype(linetype);
	break;
    }
}

TERM_PUBLIC void
PNG_point(x, y, point)
unsigned int x, y;
int point;
{
    if (png_mode == 0)
	line_and_point(x, y, point);
    else
	do_point(x, y, point);
}

#endif /* TERM_BODY */

#ifdef TERM_TABLE

TERM_TABLE_START(png_driver)
    "png",
    "Portable Network Graphics [small medium large] [monochrome gray color]",
    PNG_XMAX, PNG_YMAX, PNG_VCHAR,
    PNG_HCHAR, PNG_VTIC, PNG_HTIC, PNG_options,
    PNG_init, PNG_reset, PNG_text, null_scale,
    PNG_graphics, b_move, b_vector, PNG_linetype,
    b_put_text, b_text_angle, null_justify_text, PNG_point,
    do_arrow, set_font_null,
    0,				/* pointsize */
    TERM_CAN_MULTIPLOT | TERM_BINARY,
    0, 0, b_boxfill
TERM_TABLE_END(png_driver)

#undef LAST_TERM
#define LAST_TERM png_driver

#endif /* TERM_TABLE */

#ifdef TERM_HELP
START_HELP(png)
"1 png (OLD)",
"?commands set terminal png",
"?set terminal png",
"?set term png",
"?terminal png",
"?term png",
"?png",
" The `png` terminal driver supports Portable Network Graphics.  This old ",
" version of the png driver requires the third-party libraries \"libpng\" ",
" and \"zlib\".  There is a newer png driver, with many more features, that",
" is preferred if you have libgd version 1.8 or newer.",
"",
" Syntax:",
"       set terminal png {small | medium | large}",
"                        {transparent|notransparent}",
"                        {picsize <xsize> <ysize>}",
"                        {monochrome | gray | color}",
"                        {<color0> <color1> <color2> ...}",
"",
" `transparent` instructs the driver to generate transparent PNGs.  The first",
" color will be the transparent one.",
    "",
" The defaults are `small` (fontsize) and `color`.  Default size of the output",
" is 640*480 pixel. This can be changed by the option `picsize`.",
"",
" Each <color> must be of the form 'xrrggbb', where x is the literal character",
" 'x' and 'rrggbb' are the red, green and blue components in hex.  For example,",
" 'x00ff00' is green.  The background color is set first, then the border",
" color, then the X & Y axis color, then the plotting colors.  The maximum",
" number of colors that can be set is currently 99."
END_HELP(png)
#endif /* TERM_HELP */

#endif /* ifndef HAVE_NEWGD */