/*
** diveflag.c -- creates orange and white dive flags of various sizes
**
** Requires libpng to compile with PNG support.  See
** http://www.libpng.org/pub/png/libpng.html .
** Tested against libpng.so.3.1.2.1.  YMMV with other versions.
**
** I hereby grant this program to the public domain.  I would, however
** not legally required, appreciate credit where it is due.
**
** Beej Jorgensen (beej@beej.us)
**
** Release 1, 14-Mar-2003
**
** Release 2, 18-Mar-2003
**   Added borders, flag colors
**
** TODO:
**   cleanup after writing PNG
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#ifdef DO_PNG
#include <png.h>
#endif

#define DEFAULT_THICKNESS_CUT 5
#define MAX_FILENAME_LEN 512

typedef struct {
	int width;
	int height;
	int thickness;
	int aaflag;
	int writeppm;
	int r, g, b; // flag color
	int bordersize;
	int br, bg, bb; // border color
	char filename[MAX_FILENAME_LEN];
} IMAGE_INFO;

///////////////////////////////////////////////////////////////////////
// main()
//
int main(int argc, char **argv)
{
	void parsecl(int argc, char **argv, IMAGE_INFO *info);
	void createflag(IMAGE_INFO *info, char *data);
	void saveflagppm(IMAGE_INFO *info, char *data);
#ifdef DO_PNG
	void saveflagpng(IMAGE_INFO *info, char *data);
#endif

	IMAGE_INFO imageinfo;
	char *data;
	int datasize;

	parsecl(argc, argv, &imageinfo);

	datasize = ((imageinfo.width + imageinfo.bordersize*2) *
		(imageinfo.height + imageinfo.bordersize*2)) * 3;
	if ((data = malloc(datasize)) == NULL) {
		fprintf(stderr, "diveflag: out of memory\n");
		exit(2);
	}

	createflag(&imageinfo, data);

#ifdef DO_PNG
	if (imageinfo.writeppm)
		saveflagppm(&imageinfo, data);
	else
		saveflagpng(&imageinfo, data);
#else
	saveflagppm(&imageinfo, data);
#endif

	return 0;
}

///////////////////////////////////////////////////////////////////////
// parsecl() -- parse the command line
//
void parsecl(int argc, char **argv, IMAGE_INFO *info)
{
	int parsecolors(char *s, int *r, int *g, int *b);
	void usageexit(void);
	int i;

	info->aaflag = 0;
#ifdef DO_PNG
	info->writeppm = 0;
#else
	info->writeppm = 1;
#endif
	strcpy(info->filename, "-");

	info->r = 0xff;
	info->g = 0x3f;
	info->b = 0x00;

	info->thickness = info->width = info->height = -1; // sentinel

	info->bordersize = 0;

	for(i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-ppm")) {
			info->writeppm = 1;
		} else if (!strcmp(argv[i], "-a")) {
			info->aaflag = 1;
		} else if (!strcmp(argv[i], "-help") ||
			!strcmp(argv[i], "-h") ||
			!strcmp(argv[i], "--help") ||
			!strcmp(argv[i], "-?")) {

			usageexit();
		} else if (!strcmp(argv[i], "-c")) {
			if (i == argc-1)
				usageexit();
			if (!parsecolors(argv[++i], &info->r, &info->g, &info->b))
				usageexit();
		} else if (!strcmp(argv[i], "-bc")) {
			if (i == argc-1)
				usageexit();
			if (!parsecolors(argv[++i], &info->br, &info->bg, &info->bb))
				usageexit();
		} else if (!strcmp(argv[i], "-o")) {
			if (i == argc-1) usageexit();
			strcpy(info->filename, argv[++i]);
		} else if (!strcmp(argv[i], "-bs")) {
			if (i == argc-1) usageexit();
			info->bordersize = atoi(argv[++i]);
		} else if (info->width == -1) {
			info->width = atoi(argv[i]);
		} else if (info->height == -1) {
			info->height = atoi(argv[i]);
		} else if (info->thickness == -1) {
			info->thickness = atoi(argv[i]);
			if (info->thickness < 1) usageexit();
		} else {
			usageexit();
		}
	}

	if (info->bordersize < 0) usageexit();

	if (info->width < 1 || info->height < 1) usageexit();

	if (info->thickness == -1) {
		if (info->width < info->height)
			info->thickness = info->width / DEFAULT_THICKNESS_CUT;
		else
			info->thickness = info->height / DEFAULT_THICKNESS_CUT;
	}

	if (info->thickness < 0) usageexit();
}

///////////////////////////////////////////////////////////////////////
// parsecolors() -- parse a color string of the form r,g,b
//
int parsecolors(char *s, int *r, int *g, int *b)
{
	char *c1, *c2;

	if ((c1 = strchr(s, ',')) == NULL) return 0;
	*c1++ = '\0';
	if ((c2 = strchr(c1, ',')) == NULL) return 0;
	*c2++ = '\0';
	if (strchr(c2, ',') != NULL) return 0;

	*r = atoi(s);
	*g = atoi(c1);
	*b = atoi(c2);

	if (*r < 0 || *r > 255) return 0;
	if (*g < 0 || *g > 255) return 0;
	if (*b < 0 || *b > 255) return 0;

	return 1;
}

///////////////////////////////////////////////////////////////////////
// usageexit() -- print usage and exit
//
void usageexit(void)
{
	fprintf(stderr, "usage: diveflag [options] width height [thickness]\n");
	fprintf(stderr, "                -a             Antialias the image\n");
	fprintf(stderr, "                -ppm           Output a PPM instead of a PNG\n");
	fprintf(stderr, "                -o outfile     Specify the output file name\n");
	fprintf(stderr, "                               (\"-\" for stdout, the default)\n");
	fprintf(stderr, "                -c r,g,b       Flag colors, each from 0-255\n");
	fprintf(stderr, "                -bs border     Border size in pixels\n");
	fprintf(stderr, "                -bc r,g,b      Border colors, each from 0-255\n");

	exit(1);
}

///////////////////////////////////////////////////////////////////////
// createflag() -- render the flag to the rgb data buffer
//
// Finds the distance from the pixel to the center diagonal to
// determine if a pixel is white or orange.
//
// Not the fastest, but quite accurate. :)
//
void createflag(IMAGE_INFO *info, char *data)
{
	int i;
	int x, y;
	int t;
	int totalwidth = info->width + info->bordersize*2;
	float u, cx, cy, d, d2p1p2;
	char *pos;

	info->thickness /= 2;

	d2p1p2 = (info->width-1)*(info->width-1) + (info->height-1)*(info->height-1);

	pos = data;
	t = info->bordersize * totalwidth + info->bordersize;
	for(i = 0; i < t; i++) {
		*(pos++) = info->br;
		*(pos++) = info->bg;
		*(pos++) = info->bb;
	}

	for(y = 0; y < info->height; y++) {
		for(x = 0; x < info->width; x++) {
			u = (float)x * (info->width-1) + (float)y * (info->height-1);
			u /= d2p1p2;
			cx = u * (info->width-1);
			cy = u * (info->height-1);

			d = sqrt((x-cx)*(x-cx) + (y-cy)*(y-cy));

			if (info->aaflag && (int)d == info->thickness) {
				// use the fractional part to antialias
				float frac;
				int a;

				frac = d - (int)d;
				a = (int)(256.0 * frac);
				
				*(pos+0) = ((info->r)*a + (0xff*(0xff-a)))>>8;
				*(pos+1) = ((info->g)*a + (0xff*(0xff-a)))>>8;
				*(pos+2) = ((info->b)*a + (0xff*(0xff-a)))>>8;

			} else if (d < info->thickness) {
				*(pos+0) = 0xff;
				*(pos+1) = 0xff;
				*(pos+2) = 0xff;
			} else {
				*(pos+0) = info->r;
				*(pos+1) = info->g;
				*(pos+2) = info->b;
			}
			pos += 3;
		}

		t = info->bordersize * 2;
		for(i = 0; i < t; i++) {
			*(pos++) = info->br;
			*(pos++) = info->bg;
			*(pos++) = info->bb;
		}
	}

	t = info->bordersize * totalwidth - info->bordersize;
	for(i = 0; i < t; i++) {
		*(pos++) = info->br;
		*(pos++) = info->bg;
		*(pos++) = info->bb;
	}
}

///////////////////////////////////////////////////////////////////////
// saveflagppm() -- write the flag out as a PPM image file
//
void saveflagppm(IMAGE_INFO *info, char *data)
{
	FILE *fp;
	int bw2 = info->bordersize*2;
	int count = (info->width+bw2) * (info->height+bw2) * 3;

	if (!strcmp(info->filename, "-"))
		fp = stdout;
	else
		fp = fopen(info->filename, "wb");

	if (fp == NULL) {
		fprintf(stderr, "diveflag: error opening output file\n");
		exit(3);
	}

	fprintf(fp, "P6\n");
	fprintf(fp, "%d %d\n", info->width + bw2, info->height + bw2);
	fprintf(fp, "255\n");

	fwrite(data, sizeof(char), count, fp);

	if (fp != stdout) fclose(fp);
}

#ifdef DO_PNG
///////////////////////////////////////////////////////////////////////
// saveflagpng() -- write the flag out as a PNG image file
//
void saveflagpng(IMAGE_INFO *info, char *data)
{
	FILE *fp;
	int i;
	int bw2 = info->bordersize*2;
	png_structp png_ptr;
	png_infop info_ptr;
	png_bytep *row_pointers;

	if (!strcmp(info->filename, "-"))
		fp = stdout;
	else
		fp = fopen(info->filename, "wb");

	if (fp == NULL) {
		fprintf(stderr, "diveflag: error opening output file\n");
		exit(3);
	}

	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
		NULL, NULL, NULL);
		//(png_voidp)user_error_ptr, user_error_fn, user_warning_fn);

	if (!png_ptr) {
		fprintf(stderr, "diveflag: error creating PNG write struct\n");
		exit(4);
	}

	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
		fprintf(stderr, "diveflag: error creating PNG info\n");
		exit(4);
	}


	if (setjmp(png_jmpbuf(png_ptr))) {
		png_destroy_write_struct(&png_ptr, &info_ptr);
		fprintf(stderr, "diveflag: error writing PNG\n");
		exit(4);
	}

	png_init_io(png_ptr, fp);

	png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);

	png_set_IHDR(png_ptr, info_ptr, info->width+bw2, info->height+bw2,
		8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
		PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

	row_pointers = png_malloc(png_ptr, (info->height+bw2)*sizeof(png_bytep));
	for (i = 0; i < info->height+bw2; i++)
		row_pointers[i] = (png_bytep)(data + i * (info->width+bw2) * 3);

	png_set_rows(png_ptr, info_ptr, row_pointers);

	png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

	png_free(png_ptr, row_pointers);

	if (fp != stdout) fclose(fp);
}
#endif


