#include <png++/png.hpp>
#include <iostream>
#include <functional>

template<typename In, size_t n>
In palette_nearest(In luma)
{
	if (luma < n)
		return 0;
	return 255;
}

png::gray_pixel increase_contrast(png::gray_pixel in)
{
	return 1.2 * (in - 128) + 128;
}

template<typename Func, typename In, typename Out = decltype(std::declval<Func>()(std::declval<In>()))>
png::image<Out> independent_transform(png::image<In> input, Func fn)
{
	png::image<Out> output(input.get_width(), input.get_height());

	for (int y = 0; y < input.get_height(); y++)
		for (int x = 0; x < input.get_width(); x++)
			output.set_pixel(x, y, fn(input.get_pixel(x, y)));

	return output;
}

int main(int argc, char** argv)
{
	png::image<png::rgb_pixel> image;
	image.read(argv[1]);

	auto gray_image = independent_transform(image,
			[](auto in) {
				return png::gray_pixel(in.red * 0.2 + in.green * 0.7 + in.blue * 0.1);
			});


	auto thresholded = independent_transform(gray_image, palette_nearest<png::gray_pixel, 128>);

	thresholded.write("thresholded.png");

	for (int y = 0; y < thresholded.get_height(); y++)
	{
		for (int x = 0; x < thresholded.get_width(); x++)
		{
			int quantisation_error = thresholded[y][x] - gray_image[y][x]; 

			/* The quantisation error is spread out to give the illusion of grayscale. */
			if (x != thresholded.get_width() - 1)
			{
				thresholded[y][x+1] += quantisation_error * 4/16.0;

				if (y != thresholded.get_height() - 1)
					thresholded[y+1][x+1] += quantisation_error * 1/16.0;
			}

			if (y != (thresholded.get_height() - 1))
			{
				thresholded[y+1][x] += quantisation_error * 2/16.0;

				if (x != 0)
					thresholded[y+1][x-1] += quantisation_error * 1/16.0;
			}
		}
	}

	thresholded = independent_transform(thresholded, palette_nearest<png::gray_pixel, 140>);

	thresholded.write(argv[2]);
}
