/*
 * Copyright 2002-2008 Guillaume Cottenceau, 2015 Aleksander Denisiuk
 *
 * This software may be freely redistributed under the terms
 * of the X11 license.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#define PNG_DEBUG 3
#include <png.h>

#define OUT_FILE "initials.png"
#define WIDTH 600
#define HEIGHT 600
#define COLOR_TYPE PNG_COLOR_TYPE_RGB
#define BIT_DEPTH 8

void abort_(const char *s, ...)
{
	va_list args;
	va_start(args, s);
	vfprintf(stderr, s, args);
	fprintf(stderr, "\n");
	va_end(args);
	abort();
}

int x, y;

int width, height;
png_byte color_type;
png_byte bit_depth;

png_structp png_ptr;
png_infop info_ptr;
int number_of_passes;
png_bytep *row_pointers;

void create_png_file(void)
{
	width = WIDTH;
	height = HEIGHT;
	bit_depth = BIT_DEPTH;
	color_type = COLOR_TYPE;

	row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
	for (y = 0; y < height; y++)
		row_pointers[y] = (png_byte *)malloc(width * bit_depth * 3);
}

void write_png_file(char *file_name)
{
	FILE *fp = fopen(file_name, "wb");
	if (!fp)
		abort_("[write_png_file] File %s could not be opened for writing", file_name);

	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png_ptr)
		abort_("[write_png_file] png_create_write_struct failed");

	info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr)
		abort_("[write_png_file] png_create_info_struct failed");

	if (setjmp(png_jmpbuf(png_ptr)))
		abort_("[write_png_file] Error during init_io");

	png_init_io(png_ptr, fp);

	if (setjmp(png_jmpbuf(png_ptr)))
		abort_("[write_png_file] Error during writing header");

	png_set_IHDR(png_ptr, info_ptr, width, height,
		     bit_depth, color_type, PNG_INTERLACE_NONE,
		     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

	png_write_info(png_ptr, info_ptr);

	if (setjmp(png_jmpbuf(png_ptr)))
		abort_("[write_png_file] Error during writing bytes");

	png_write_image(png_ptr, row_pointers);

	if (setjmp(png_jmpbuf(png_ptr)))
		abort_("[write_png_file] Error during end of write");

	png_write_end(png_ptr, NULL);

	for (y = 0; y < height; y++)
		free(row_pointers[y]);
	free(row_pointers);

	fclose(fp);
}

typedef struct {
	int x;
	int y;
} Point;

void write_pixel(int px, int py, png_byte cr, png_byte cg, png_byte cb)
{
	if (px < 0 || py < 0 || px >= width || py >= height)
		return;
	png_byte *row = row_pointers[py];
	png_byte *ptr = &(row[px * 3]);
	ptr[0] = cr;
	ptr[1] = cg;
	ptr[2] = cb;
}

void bresenham(int i1, int j1, int i2, int j2, png_byte cr, png_byte cg, png_byte cb)
{
	int dx = abs(i2 - i1);
	int dy = abs(j2 - j1);
	int sx = (i1 < i2) ? 1 : -1;
	int sy = (j1 < j2) ? 1 : -1;
	int err = dx - dy;
	int x0 = i1, y0 = j1;

	for (;;) {
		write_pixel(x0, y0, cr, cg, cb);
		if (x0 == i2 && y0 == j2)
			break;
		int e2 = 2 * err;
		if (e2 > -dy) {
			err -= dy;
			x0 += sx;
		}
		if (e2 < dx) {
			err += dx;
			y0 += sy;
		}
	}
}

static void rysuj_obrys_wielokata(const Point *v, int n,
				  png_byte cr, png_byte cg, png_byte cb)
{
	int i;
	for (i = 0; i < n; i++) {
		int j = (i + 1) % n;
		bresenham(v[i].x, v[i].y, v[j].x, v[j].y, cr, cg, cb);
	}
}

static void elipsa_4_sym(int xc, int yc, int xp, int yp,
			 png_byte cr, png_byte cg, png_byte cb)
{
	write_pixel(xc + xp, yc + yp, cr, cg, cb);
	write_pixel(xc - xp, yc + yp, cr, cg, cb);
	write_pixel(xc + xp, yc - yp, cr, cg, cb);
	write_pixel(xc - xp, yc - yp, cr, cg, cb);
}

static void elipsa_midpoint(int xc, int yc, int rx, int ry,
			    png_byte cr, png_byte cg, png_byte cb)
{
	double rx2 = (double)rx * (double)rx;
	double ry2 = (double)ry * (double)ry;
	long long x = 0;
	long long y = ry;
	double dx = 2.0 * ry2 * (double)x;
	double dy = 2.0 * rx2 * (double)y;
	double d1 = ry2 - rx2 * (double)ry + 0.25 * rx2;

	while (dx < dy) {
		elipsa_4_sym(xc, yc, (int)x, (int)y, cr, cg, cb);
		x++;
		dx += 2.0 * ry2;
		if (d1 < 0) {
			d1 += dx + ry2;
		} else {
			y--;
			dy -= 2.0 * rx2;
			d1 += dx - dy + ry2;
		}
	}

	double d2 = ry2 * ((double)x * (double)x + (double)x)
		  + rx2 * ((double)y * (double)y - (double)y)
		  - rx2 * ry2;

	while (y >= 0) {
		elipsa_4_sym(xc, yc, (int)x, (int)y, cr, cg, cb);
		y--;
		dy -= 2.0 * rx2;
		if (d2 > 0) {
			d2 += rx2 - dy;
		} else {
			x++;
			dx += 2.0 * ry2;
			d2 += dx - dy + rx2;
		}
	}
}

static int piksel_rgb(int px, int py, png_byte r, png_byte g, png_byte b)
{
	if (px < 0 || py < 0 || px >= width || py >= height)
		return 0;
	png_byte *p = &(row_pointers[py][px * 3]);
	return p[0] == r && p[1] == g && p[2] == b;
}

static void flood_fill_4spojny(int sx, int sy,
			     png_byte nr, png_byte ng, png_byte nb)
{
	size_t np;
	unsigned char *been;
	Point *q;
	size_t qt, qh;
	png_byte or, og, ob;
	const int dx4[4] = { 1, -1, 0, 0 };
	const int dy4[4] = { 0, 0, 1, -1 };

	if (sx < 0 || sy < 0 || sx >= width || sy >= height)
		return;
	{
		png_byte *p0 = &(row_pointers[sy][sx * 3]);
		or = p0[0];
		og = p0[1];
		ob = p0[2];
	}
	if (or == nr && og == ng && ob == nb)
		return;

	np = (size_t)width * (size_t)height;
	been = (unsigned char *)calloc(np, 1);
	q = (Point *)malloc(np * sizeof(Point));
	if (!been || !q) {
		free(been);
		free(q);
		abort_("flood_fill_4spojny: brak pamięci");
	}

	qt = 0;
	q[qt].x = sx;
	q[qt].y = sy;
	qt++;
	been[(size_t)sy * width + sx] = 1;

	qh = 0;
	while (qh < qt) {
		int x = q[qh].x;
		int y = q[qh].y;
		int k;
		qh++;

		if (!piksel_rgb(x, y, or, og, ob))
			continue;
		write_pixel(x, y, nr, ng, nb);

		for (k = 0; k < 4; k++) {
			int nx = x + dx4[k];
			int ny = y + dy4[k];
			size_t id;
			if (nx < 0 || ny < 0 || nx >= width || ny >= height)
				continue;
			id = (size_t)ny * width + nx;
			if (been[id])
				continue;
			if (!piksel_rgb(nx, ny, or, og, ob))
				continue;
			been[id] = 1;
			if (qt >= np)
				break;
			q[qt].x = nx;
			q[qt].y = ny;
			qt++;
		}
	}

	free(q);
	free(been);
}

void process_file(void)
{
	for (y = 0; y < height; y++) {
		png_byte *row = row_pointers[y];
		for (x = 0; x < width; x++) {
			png_byte *ptr = &(row[x * 3]);
			ptr[0] = 0;
			ptr[1] = ptr[2] = 255;
		}
	}

	{
		const int xmin = 200;
		const int xmax = 533;
		const int ymin = 186;
		const int ymax = 313;
		const int marg = 35;
		int xc = (xmin + xmax) / 2;
		int yc = (ymin + ymax) / 2;
		int rx = (xmax - xmin) / 2 + marg;
		int ry = (ymax - ymin) / 2 + marg;
		if (rx < 1)
			rx = 1;
		if (ry < 1)
			ry = 1;
		elipsa_midpoint(xc, yc, rx, ry, 0, 0, 0);
	}

	{
		const int n = 12;
		const Point m1[] = {
			{ 200, 186 }, { 200, 313 }, { 226, 313 }, { 226, 219 },
			{ 266, 246 }, { 306, 246 }, { 306, 313 }, { 333, 313 },
			{ 333, 186 }, { 306, 186 }, { 266, 213 }, { 226, 186 }
		};
		const Point m2[] = {
			{ 400, 186 }, { 400, 313 }, { 426, 313 }, { 426, 219 },
			{ 466, 246 }, { 506, 246 }, { 506, 313 }, { 533, 313 },
			{ 533, 186 }, { 506, 186 }, { 466, 213 }, { 426, 186 }
		};

		rysuj_obrys_wielokata(m1, n, 0, 0, 0);
		rysuj_obrys_wielokata(m2, n, 0, 0, 0);
	}

	flood_fill_4spojny(268, 275, 255, 0, 255);   
	flood_fill_4spojny(468, 275, 255, 200, 64); 
	flood_fill_4spojny(367, 248, 255, 255, 0); 
	flood_fill_4spojny(8, 8, 255, 160, 180);     
}

int main(int argc, char **argv)
{
	create_png_file();
	process_file();
	write_png_file(OUT_FILE);

	return 0;
}
