/**
 * Copyright (C) 2016-2017 IRIE Shinsuke
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * smaa_areatex.cpp  version 0.4.0
 *
 * This is a part of smaa-cpp that is an implementation of
 * Enhanced Subpixel Morphological Antialiasing (SMAA) written in C++.
 *
 * This program is C++ rewrite of AreaTex.py included in the original
 * SMAA ditribution:
 *
 *   https://github.com/iryoku/smaa/tree/master/Scripts
 */

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <cmath>

/*------------------------------------------------------------------------------*/
/* Type Definitions */

class Int2;
class Dbl2;

class Int2 {
public:
	int x, y;

	Int2() { this->x = this->y = 0; }
	Int2(int x) { this->x = this->y = x; }
	Int2(int x, int y) { this->x = x; this->y = y; }

	operator Dbl2();

	Int2 operator + (Int2 other) { return Int2(x + other.x, y + other.y); }
	Int2 operator * (Int2 other) { return Int2(x * other.x, y * other.y); }
};

class Dbl2 {
public:
	double x, y;

	Dbl2() { this->x = this->y = 0.0; }
	Dbl2(double x) { this->x = this->y = x; }
	Dbl2(double x, double y) { this->x = x; this->y = y; }

	Dbl2 apply(double (* func)(double)) { return Dbl2(func(x), func(y)); }

	operator Int2();

	Dbl2 operator + (Dbl2 other) { return Dbl2(x + other.x, y + other.y); }
	Dbl2 operator - (Dbl2 other) { return Dbl2(x - other.x, y - other.y); }
	Dbl2 operator * (Dbl2 other) { return Dbl2(x * other.x, y * other.y); }
	Dbl2 operator / (Dbl2 other) { return Dbl2(x / other.x, y / other.y); }
	Dbl2 operator += (Dbl2 other) { return Dbl2(x += other.x, y += other.y); }
	bool operator == (Dbl2 other) { return (x == other.x && y == other.y); }
};

Int2::operator Dbl2() { return Dbl2((double)x, (double)y); }
Dbl2::operator Int2() { return Int2((int)x, (int)y); }

/*------------------------------------------------------------------------------*/
/* Data to Calculate Areatex */

/* Texture sizes: */
/* (it's quite possible that this is not easily configurable) */
static const int SUBSAMPLES_ORTHO = 7;
static const int SUBSAMPLES_DIAG  = 5;
static const int MAX_DIST_ORTHO_COMPAT = 16;
static const int MAX_DIST_ORTHO = 20;
static const int MAX_DIST_DIAG  = 20;
static const int TEX_SIZE_ORTHO = 80; /* 16 * 5 slots = 80 */
static const int TEX_SIZE_DIAG  = 80; /* 20 * 4 slots = 80 */

/* Number of samples for calculating areas in the diagonal textures: */
/* (diagonal areas are calculated using brute force sampling) */
static const int SAMPLES_DIAG = 30;

/* Maximum distance for smoothing u-shapes: */
static const int SMOOTH_MAX_DISTANCE = 32;

/*------------------------------------------------------------------------------*/
/* Offset Tables */

/* Offsets for subsample rendering */
static const double subsample_offsets_ortho[SUBSAMPLES_ORTHO] = {
	0.0,    /* 0 */
	-0.25,  /* 1 */
	0.25,   /* 2 */
	-0.125, /* 3 */
	0.125,  /* 4 */
	-0.375, /* 5 */
	0.375   /* 6 */
};

static const Dbl2 subsample_offsets_diag[SUBSAMPLES_DIAG] = {
	{ 0.00,   0.00},  /* 0 */
	{ 0.25,  -0.25},  /* 1 */
	{-0.25,   0.25},  /* 2 */
	{ 0.125, -0.125}, /* 3 */
	{-0.125,  0.125}  /* 4 */
};

/* Mapping offsets for placing each pattern subtexture into its place */
enum edgesorthoIndices
{
	EDGESORTHO_NONE_NONE = 0,
	EDGESORTHO_NONE_NEGA = 1,
	EDGESORTHO_NONE_POSI = 2,
	EDGESORTHO_NONE_BOTH = 3,
	EDGESORTHO_NEGA_NONE = 4,
	EDGESORTHO_NEGA_NEGA = 5,
	EDGESORTHO_NEGA_POSI = 6,
	EDGESORTHO_NEGA_BOTH = 7,
	EDGESORTHO_POSI_NONE = 8,
	EDGESORTHO_POSI_NEGA = 9,
	EDGESORTHO_POSI_POSI = 10,
	EDGESORTHO_POSI_BOTH = 11,
	EDGESORTHO_BOTH_NONE = 12,
	EDGESORTHO_BOTH_NEGA = 13,
	EDGESORTHO_BOTH_POSI = 14,
	EDGESORTHO_BOTH_BOTH = 15,
};

static const Int2 edgesortho_compat[16] = {
	{0, 0}, {0, 1}, {0, 3}, {0, 4}, {1, 0}, {1, 1}, {1, 3}, {1, 4},
	{3, 0}, {3, 1}, {3, 3}, {3, 4}, {4, 0}, {4, 1}, {4, 3}, {4, 4}
};

static const Int2 edgesortho[16] = {
	{0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3},
	{2, 0}, {2, 1}, {2, 2}, {2, 3}, {3, 0}, {3, 1}, {3, 2}, {3, 3}
};

enum edgesdiagIndices
{
	EDGESDIAG_NONE_NONE = 0,
	EDGESDIAG_NONE_VERT = 1,
	EDGESDIAG_NONE_HORZ = 2,
	EDGESDIAG_NONE_BOTH = 3,
	EDGESDIAG_VERT_NONE = 4,
	EDGESDIAG_VERT_VERT = 5,
	EDGESDIAG_VERT_HORZ = 6,
	EDGESDIAG_VERT_BOTH = 7,
	EDGESDIAG_HORZ_NONE = 8,
	EDGESDIAG_HORZ_VERT = 9,
	EDGESDIAG_HORZ_HORZ = 10,
	EDGESDIAG_HORZ_BOTH = 11,
	EDGESDIAG_BOTH_NONE = 12,
	EDGESDIAG_BOTH_VERT = 13,
	EDGESDIAG_BOTH_HORZ = 14,
	EDGESDIAG_BOTH_BOTH = 15,
};

static const Int2 edgesdiag[16] = {
	{0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 0}, {1, 1}, {1, 2}, {1, 3},
	{2, 0}, {2, 1}, {2, 2}, {2, 3}, {3, 0}, {3, 1}, {3, 2}, {3, 3}
};

/*------------------------------------------------------------------------------*/
/* Miscellaneous Utility Functions */

/* Linear interpolation: */
static Dbl2 lerp(Dbl2 a, Dbl2 b, double p)
{
	return a + (b - a) * Dbl2(p);
}

/* Saturates a value to [0..1] range: */
static double saturate(double x)
{
	return 0.0 < x ? (x < 1.0 ? x : 1.0) : 0.0;
}

/*------------------------------------------------------------------------------*/
/* Horizontal/Vertical Areas */

class AreaOrtho {
	double m_data[SUBSAMPLES_ORTHO][TEX_SIZE_ORTHO][TEX_SIZE_ORTHO][2];
	bool m_compat;
	bool m_orig_u;
public:
	AreaOrtho(bool compat, bool orig_u) : m_compat(compat), m_orig_u(orig_u) {}

	double *getData() { return (double *)&m_data; }
	Dbl2 getPixel(int offset_index, Int2 coords) {
		return Dbl2(m_data[offset_index][coords.y][coords.x][0],
			    m_data[offset_index][coords.y][coords.x][1]);
	}

	void areaTex(int offset_index);
private:
	void putPixel(int offset_index, Int2 coords, Dbl2 pixel) {
		m_data[offset_index][coords.y][coords.x][0] = pixel.x;
		m_data[offset_index][coords.y][coords.x][1] = pixel.y;
	}

	Dbl2 smoothArea(double d, Dbl2 a1, Dbl2 a2);
	Dbl2 makeQuad(int x, double d, double o);
	Dbl2 area(Dbl2 p1, Dbl2 p2, int x);
	Dbl2 calculate(int pattern, int left, int right, double offset);
};

/* Smoothing function for small u-patterns: */
Dbl2 AreaOrtho::smoothArea(double d, Dbl2 a1, Dbl2 a2)
{
	Dbl2 b1 = (a1 * Dbl2(2.0)).apply(sqrt) * Dbl2(0.5);
	Dbl2 b2 = (a2 * Dbl2(2.0)).apply(sqrt) * Dbl2(0.5);
	double p = saturate(d / (double)SMOOTH_MAX_DISTANCE);
	return lerp(b1, a1, p) + lerp(b2, a2, p);
}

/* Smoothing u-patterns by quadratic function: */
Dbl2 AreaOrtho::makeQuad(int x, double d, double o)
{
	double r = (double)x;

	/* fmin() below is a trick to smooth tiny u-patterns: */
	return Dbl2(r, (1.0 - fmin(4.0, d) * r * (d - r) / (d * d)) * o);
}

/* Calculates the area under the line p1->p2, for the pixel x..x+1: */
Dbl2 AreaOrtho::area(Dbl2 p1, Dbl2 p2, int x)
{
	Dbl2 d = p2 - p1;
	double x1 = (double)x;
	double x2 = x1 + 1.0;

	if ((x1 >= p1.x && x1 < p2.x) || (x2 > p1.x && x2 <= p2.x)) { /* inside? */
		double y1 = p1.y + (x1 - p1.x) * d.y / d.x;
		double y2 = p1.y + (x2 - p1.x) * d.y / d.x;

		if ((copysign(1.0, y1) == copysign(1.0, y2) ||
		     fabs(y1) < 1e-4 || fabs(y2) < 1e-4)) { /* trapezoid? */
			double a = (y1 + y2) / 2.0;
			if (a < 0.0)
				return Dbl2(fabs(a), 0.0);
			else
				return Dbl2(0.0, fabs(a));
		}
		else { /* Then, we got two triangles: */
			double x = p1.x - p1.y * d.x / d.y, xi;
			double a1 = x > p1.x ? y1 * modf(x, &xi) / 2.0 : 0.0;
			double a2 = x < p2.x ? y2 * (1.0 - modf(x, &xi)) / 2.0 : 0.0;
			double a = fabs(a1) > fabs(a2) ? a1 : -a2;
			if (a < 0.0)
				return Dbl2(fabs(a1), fabs(a2));
			else
				return Dbl2(fabs(a2), fabs(a1));
		}
	}
	else
		return Dbl2(0.0, 0.0);
}

/* Calculates the area for a given pattern and distances to the left and to the */
/* right, biased by an offset: */
Dbl2 AreaOrtho::calculate(int pattern, int left, int right, double offset)
{
	Dbl2 a1, a2;

	/*
	 * o1           |
	 *      .-------´
	 * o2   |
	 *
	 *      <---d--->
	 */
	double d = (double)(left + right + 1);

	double o1 = 0.5 + offset;
	double o2 = 0.5 + offset - 1.0;

	switch (pattern) {
		case EDGESORTHO_NONE_NONE:
		{
			/*
			 *
			 *    ------
			 *
			 */
			return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_POSI_NONE:
		{
			/*
			 *
			 *   .------
			 *   |
			 *
			 * We only offset L patterns in the crossing edge side, to make it
			 * converge with the unfiltered pattern 0 (we don't want to filter the
			 * pattern 0 to avoid artifacts).
			 */
			if (left <= right)
				return area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left);
			else
				return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_NONE_POSI:
		{
			/*
			 *
			 *    ------.
			 *          |
			 */
			if (left >= right)
				return area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left);
			else
				return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_POSI_POSI:
		{
			/*
			 *
			 *   .------.
			 *   |      |
			 */
			if (m_orig_u) {
				a1 = area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left);
				a2 = area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left);
				return smoothArea(d, a1, a2);
			}
			else
				return area(makeQuad(left, d, o2), makeQuad(left + 1, d, o2), left);
			break;
		}
		case EDGESORTHO_NEGA_NONE:
		{
			/*
			 *   |
			 *   `------
			 *
			 */
			if (left <= right)
				return area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left);
			else
				return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_BOTH_NONE:
		{
			/*
			 *   |
			 *   +------
			 *   |
			 */
			return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_NEGA_POSI:
		{
			/*
			 *   |
			 *   `------.
			 *          |
			 *
			 * A problem of not offseting L patterns (see above), is that for certain
			 * max search distances, the pixels in the center of a Z pattern will
			 * detect the full Z pattern, while the pixels in the sides will detect a
			 * L pattern. To avoid discontinuities, we blend the full offsetted Z
			 * revectorization with partially offsetted L patterns.
			 */
			if (fabs(offset) > 0.0) {
				a1 = area(Dbl2(0.0, o1), Dbl2(d, o2), left);
				a2 = area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left);
				a2 += area(Dbl2(d / 2.0, 0.0), Dbl2(d, o2), left);
				return (a1 + a2) / Dbl2(2.0);
			}
			else
				return area(Dbl2(0.0, o1), Dbl2(d, o2), left);
			break;
		}
		case EDGESORTHO_BOTH_POSI:
		{
			/*
			 *   |
			 *   +------.
			 *   |      |
			 */
			return area(Dbl2(0.0, o1), Dbl2(d, o2), left);
			break;
		}
		case EDGESORTHO_NONE_NEGA:
		{
			/*
			 *          |
			 *    ------´
			 *
			 */
			if (left >= right)
				return area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left);
			else
				return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_POSI_NEGA:
		{
			/*
			 *          |
			 *   .------´
			 *   |
			 */
			if (fabs(offset) > 0.0) {
				a1 = area(Dbl2(0.0, o2), Dbl2(d, o1), left);
				a2 = area(Dbl2(0.0, o2), Dbl2(d / 2.0, 0.0), left);
				a2 += area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left);
				return (a1 + a2) / Dbl2(2.0);
			}
			else
				return area(Dbl2(0.0, o2), Dbl2(d, o1), left);
			break;
		}
		case EDGESORTHO_NONE_BOTH:
		{
			/*
			 *          |
			 *    ------+
			 *          |
			 */
			return Dbl2(0.0, 0.0);
			break;
		}
		case EDGESORTHO_POSI_BOTH:
		{
			/*
			 *          |
			 *   .------+
			 *   |      |
			 */
			return area(Dbl2(0.0, o2), Dbl2(d, o1), left);
			break;
		}
		case EDGESORTHO_NEGA_NEGA:
		{
			/*
			 *   |      |
			 *   `------´
			 *
			 */
			if (m_orig_u) {
				a1 = area(Dbl2(0.0, o1), Dbl2(d / 2.0, 0.0), left);
				a2 = area(Dbl2(d / 2.0, 0.0), Dbl2(d, o1), left);
				return smoothArea(d, a1, a2);
			}
			else
				return area(makeQuad(left, d, o1), makeQuad(left + 1, d, o1), left);
			break;
		}
		case EDGESORTHO_BOTH_NEGA:
		{
			/*
			 *   |      |
			 *   +------´
			 *   |
			 */
			return area(Dbl2(0.0, o2), Dbl2(d, o1), left);
			break;
		}
		case EDGESORTHO_NEGA_BOTH:
		{
			/*
			 *   |      |
			 *   `------+
			 *          |
			 */
			return area(Dbl2(0.0, o1), Dbl2(d, o2), left);
			break;
		}
		case EDGESORTHO_BOTH_BOTH:
		{
			/*
			 *   |      |
			 *   +------+
			 *   |      |
			 */
			return Dbl2(0.0, 0.0);
			break;
		}
	}

	return Dbl2(0.0, 0.0);
}

/*------------------------------------------------------------------------------*/
/* Diagonal Areas */

class AreaDiag {
	double m_data[SUBSAMPLES_DIAG][TEX_SIZE_DIAG][TEX_SIZE_DIAG][2];
	bool m_numeric;
	bool m_orig_u;
public:
	AreaDiag(bool numeric, bool orig_u) : m_numeric(numeric), m_orig_u(orig_u) {}

	double *getData() { return (double *)&m_data; }
	Dbl2 getPixel(int offset_index, Int2 coords) {
		return Dbl2(m_data[offset_index][coords.y][coords.x][0],
			    m_data[offset_index][coords.y][coords.x][1]);
	}

	void areaTex(int offset_index);
private:
	void putPixel(int offset_index, Int2 coords, Dbl2 pixel) {
		m_data[offset_index][coords.y][coords.x][0] = pixel.x;
		m_data[offset_index][coords.y][coords.x][1] = pixel.y;
	}

	double area1(Dbl2 p1, Dbl2 p2, Int2 p);
	Dbl2 area(Dbl2 p1, Dbl2 p2, int left);
	Dbl2 areaTriangle(Dbl2 p1L, Dbl2 p2L, Dbl2 p1R, Dbl2 p2R, int left);
	Dbl2 calculate(int pattern, int left, int right, Dbl2 offset);
};

/* Calculates the area under the line p1->p2 for the pixel 'p' using brute */
/* force sampling: */
/* (quick and dirty solution, but it works) */
double AreaDiag::area1(Dbl2 p1, Dbl2 p2, Int2 p)
{
	if (p1 == p2)
		return 1.0;

	double xm = (p1.x + p2.x) / 2.0, ym = (p1.y + p2.y) / 2.0;
	double a = p2.y - p1.y;
	double b = p1.x - p2.x;
	int count = 0;

	for (int ix = 0; ix < SAMPLES_DIAG; ix++) {
		double x = (double)p.x + (double)ix / (double)(SAMPLES_DIAG - 1);
		for (int iy = 0; iy < SAMPLES_DIAG; iy++) {
			double y = (double)p.y + (double)iy / (double)(SAMPLES_DIAG - 1);
			if (a * (x - xm) + b * (y - ym) > 0.0) /* inside? */
				count++;
		}
	}
	return (double)count / (double)(SAMPLES_DIAG * SAMPLES_DIAG);
}

/* Calculates the area under the line p1->p2: */
/* (includes the pixel and its opposite) */
Dbl2 AreaDiag::area(Dbl2 p1, Dbl2 p2, int left)
{
	if (m_numeric) {
		double a1 = area1(p1, p2, Int2(1, 0) + Int2(left));
		double a2 = area1(p1, p2, Int2(1, 1) + Int2(left));
		return Dbl2(1.0 - a1, a2);
	}

	/* Calculates the area under the line p1->p2 for the pixel 'p' analytically */
	Dbl2 d = p2 - p1;
	if (d.x == 0.0)
		return Dbl2(0.0, 1.0);
	if (d.y == 0.0)
		return Dbl2(1.0, 0.0);

	double x1 = (double)(1 + left);
	double x2 = x1 + 1.0;
	double ymid = x1;
	double xtop = p1.x + (ymid + 1.0 - p1.y) * d.x / d.y;
	double xmid = p1.x + (ymid       - p1.y) * d.x / d.y;
	double xbot = p1.x + (ymid - 1.0 - p1.y) * d.x / d.y;

	double y1 = p1.y + (x1 - p1.x) * d.y / d.x;
	double y2 = p1.y + (x2 - p1.x) * d.y / d.x;
	double fy1 = y1 - floor(y1);
	double fy2 = y2 - floor(y2);
	int iy1 = (int)floor(y1 - ymid);
	int iy2 = (int)floor(y2 - ymid);

	if (iy1 <= -2) {
		if (iy2 == -1)
			return Dbl2(1.0 - (x2 - xbot) * fy2 * 0.5, 0.0);
		else if (iy2 == 0)
			return Dbl2((xmid + xbot) * 0.5 - x1, (x2 - xmid) * fy2 * 0.5);
		else if (iy2 >= 1)
			return Dbl2((xmid + xbot) * 0.5 - x1, x2 -  (xtop + xmid) * 0.5);
		else /* iy2 < -1 */
			return Dbl2(1.0, 0.0);
	}
	else if (iy1 == -1) {
		if (iy2 == -1)
			return Dbl2(1.0 - (fy1 + fy2) * 0.5, 0.0);
		else if (iy2 == 0)
			return Dbl2((xmid - x1) * (1.0 - fy1) * 0.5, (x2 - xmid) * fy2 * 0.5);
		else if (iy2 >= 1)
			return Dbl2((xmid - x1) * (1.0 - fy1) * 0.5, x2 - (xtop + xmid) * 0.5);
		else /* iy2 < -1 */
			return Dbl2(1.0 - (xbot - x1) * fy1 * 0.5, 0.0);
	}
	else if (iy1 == 0) {
		if (iy2 == -1)
			return Dbl2((x2 - xmid) * (1.0 - fy2) * 0.5, (xmid - x1) * fy1 * 0.5);
		else if (iy2 == 0)
			return Dbl2(0.0, (fy1 + fy2) * 0.5);
		else if (iy2 >= 1)
			return Dbl2(0.0, 1.0 - (xtop - x1) * (1.0 - fy1) * 0.5);
		else /* iy2 < -1 */
			return Dbl2(x2 - (xmid + xbot) * 0.5, (xmid - x1) * fy1 * 0.5);
	}
	else { /* iy1 > 0 */
		if (iy2 == -1)
			return Dbl2((x2 - xtop) * (1.0 - fy2) * 0.5, (xtop + xmid) * 0.5 - x1);
		else if (iy2 == 0)
			return Dbl2(0.0, 1.0 - (x1 - xtop) * (1.0 - fy2) * 0.5);
		else if (iy2 >= 1)
			return Dbl2(0.0, 1.0);
		else /* iy2 < -1 */
			return Dbl2(x2 - (xmid + xbot) * 0.5, (xtop + xmid) * 0.5 - x1);
	}
}

/* Calculate u-patterns using a triangle: */
Dbl2 AreaDiag::areaTriangle(Dbl2 p1L, Dbl2 p2L, Dbl2 p1R, Dbl2 p2R, int left)
{
	double x1 = (double)(1 + left);
	double x2 = x1 + 1.0;

	Dbl2 dL = p2L - p1L;
	Dbl2 dR = p2R - p1R;
	double xm = ((p1L.x * dL.y / dL.x - p1L.y) - (p1R.x * dR.y / dR.x - p1R.y)) / (dL.y / dL.x - dR.y / dR.x);

	double y1 = (x1 < xm) ? p1L.y + (x1 - p1L.x) * dL.y / dL.x : p1R.y + (x1 - p1R.x) * dR.y / dR.x;
	double y2 = (x2 < xm) ? p1L.y + (x2 - p1L.x) * dL.y / dL.x : p1R.y + (x2 - p1R.x) * dR.y / dR.x;

	return area(Dbl2(x1, y1), Dbl2(x2, y2), left);
}

/* Calculates the area for a given pattern and distances to the left and to the */
/* right, biased by an offset: */
Dbl2 AreaDiag::calculate(int pattern, int left, int right, Dbl2 offset)
{
	Dbl2 a1, a2;

	double d = (double)(left + right + 1);

	/*
	 * There is some Black Magic around diagonal area calculations. Unlike
	 * orthogonal patterns, the 'null' pattern (one without crossing edges) must be
	 * filtered, and the ends of both the 'null' and L patterns are not known: L
	 * and U patterns have different endings, and we don't know what is the
	 * adjacent pattern. So, what we do is calculate a blend of both possibilites.
	 */
	switch (pattern) {
		case EDGESDIAG_NONE_NONE:
		{
			/*
			 *
			 *         .-´
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 *
			 */
			a1 = area(Dbl2(1.0, 1.0), Dbl2(1.0, 1.0) + Dbl2(d), left); /* 1st possibility */
			a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d), left); /* 2nd possibility */
			return (a1 + a2) / Dbl2(2.0); /* Blend them */
			break;
		}
		case EDGESDIAG_VERT_NONE:
		{
			/*
			 *
			 *         .-´
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */
			a1 = area(Dbl2(1.0, 0.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left);
			a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_NONE_HORZ:
		{
			/*
			 *
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 *
			 */
			a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_VERT_HORZ:
		{
			/*
			 *
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */
			if (m_orig_u)
				return area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			else
				return areaTriangle(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d),
						    Dbl2(0.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			break;
		}
		case EDGESDIAG_HORZ_NONE:
		{
			/*
			 *
			 *         .-´
			 *       .-´
			 *     .-´
			 * ----´
			 *
			 *
			 */
			a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left);
			a2 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_BOTH_NONE:
		{
			/*
			 *
			 *         .-´
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */
			a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(0.0, 0.0) + Dbl2(d), left);
			a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d), left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_HORZ_HORZ:
		{
			/*
			 *
			 *         .----
			 *       .-´
			 *     .-´
			 * ----´
			 *
			 *
			 */
			return area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			break;
		}
		case EDGESDIAG_BOTH_HORZ:
		{
			/*
			 *
			 *         .----
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */
			a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_NONE_VERT:
		{
			/*
			 *         |
			 *         |
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 *
			 */
			a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_VERT_VERT:
		{
			/*
			 *         |
			 *         |
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */
			return area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			break;
		}
		case EDGESDIAG_NONE_BOTH:
		{
			/*
			 *         |
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 *
			 */
			a1 = area(Dbl2(0.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0), Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_VERT_BOTH:
		{
			/*
			 *         |
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */
			a1 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_HORZ_VERT:
		{
			/*
			 *         |
			 *         |
			 *       .-´
			 *     .-´
			 * ----´
			 *
			 *
			 */
			if (m_orig_u)
				return area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			else
				return areaTriangle(Dbl2(1.0, 1.0) + offset, Dbl2(2.0, 1.0) + Dbl2(d),
						    Dbl2(1.0, 0.0), Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			break;
		}
		case EDGESDIAG_BOTH_VERT:
		{
			/*
			 *         |
			 *         |
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */
			a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_HORZ_BOTH:
		{
			/*
			 *         |
			 *         .----
			 *       .-´
			 *     .-´
			 * ----´
			 *
			 *
			 */
			a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
		case EDGESDIAG_BOTH_BOTH:
		{
			/*
			 *         |
			 *         .----
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */
			a1 = area(Dbl2(1.0, 1.0) + offset, Dbl2(1.0, 1.0) + Dbl2(d) + offset, left);
			a2 = area(Dbl2(1.0, 0.0) + offset, Dbl2(1.0, 0.0) + Dbl2(d) + offset, left);
			return (a1 + a2) / Dbl2(2.0);
			break;
		}
	}

	return Dbl2(0.0, 0.0);
}

/*------------------------------------------------------------------------------*/
/* Main Loops */

void AreaOrtho::areaTex(int offset_index)
{
	double offset = subsample_offsets_ortho[offset_index];
	int max_dist = m_compat ? MAX_DIST_ORTHO_COMPAT : MAX_DIST_ORTHO;

	for (int pattern = 0; pattern < 16; pattern++) {
		Int2 e = Int2(max_dist) * (m_compat ? edgesortho_compat : edgesortho)[pattern];
		for (int left = 0; left < max_dist; left++) {
			for (int right = 0; right < max_dist; right++) {
				Dbl2 p = calculate(pattern, left * left, right * right, offset);
				Int2 coords = e + Int2(left, right);

				putPixel(offset_index, coords, p);
			}
		}
	}
	return;
}

void AreaDiag::areaTex(int offset_index)
{
	Dbl2 offset = subsample_offsets_diag[offset_index];

	for (int pattern = 0; pattern < 16; pattern++) {
		Int2 e = Int2(MAX_DIST_DIAG) * edgesdiag[pattern];
		for (int left = 0; left < MAX_DIST_DIAG; left++) {
			for (int right = 0; right < MAX_DIST_DIAG; right++) {
				Dbl2 p = calculate(pattern, left, right, offset);
				Int2 coords = e + Int2(left, right);

				putPixel(offset_index, coords, p);
			}
		}
	}
	return;
}

/*------------------------------------------------------------------------------*/
/* Write File to Specified Location on Disk */

/* C/C++ source code (arrays of floats) */
static void write_double_array(FILE *fp, const double *ptr, int length, const char *array_name, bool quantize)
{
	fprintf(fp, "static const float %s[%d] = {", array_name, length);

	for (int n = 0; n < length; n++) {
		if (n > 0)
			fprintf(fp, ",");
		fprintf(fp, (n % 8 != 0) ? " " : "\n\t");

		if (quantize)
			fprintf(fp, "%3d / 255.0", (int)(*(ptr++) * 255.0));
		else
			fprintf(fp, "%1.8lf", *(ptr++));
	}

	fprintf(fp, "\n};\n");
}

static void write_csource(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsampling, bool quantize)
{
	fprintf(fp, "/* This file was generated by smaa_areatex.cpp */\n");

	fprintf(fp, "\n/* Horizontal/Vertical Areas */\n");
	write_double_array(fp, ortho->getData(),
			   TEX_SIZE_ORTHO * TEX_SIZE_ORTHO * 2 * (subsampling ? SUBSAMPLES_ORTHO : 1),
			   "areatex", quantize);

	fprintf(fp, "\n/* Diagonal Areas */\n");
	write_double_array(fp, diag->getData(),
			   TEX_SIZE_DIAG * TEX_SIZE_DIAG * 2 * (subsampling ? SUBSAMPLES_DIAG : 1),
			   "areatex_diag", quantize);
}

/* .tga File (RGBA 32bit uncompressed) */
static void write_tga(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsampling)
{
	int subsamples = subsampling ? SUBSAMPLES_ORTHO : 1;
	unsigned char header[18] = {0, 0,
				    2,   /* uncompressed RGB */
				    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				    32,  /* 32bit */
				    8};  /* 8bit alpha, left to right, bottom to top */

	/* Set width and height */
	header[12] = (TEX_SIZE_ORTHO + TEX_SIZE_DIAG)      & 0xff;
	header[13] = ((TEX_SIZE_ORTHO + TEX_SIZE_DIAG) >> 8) & 0xff;
	header[14] = (subsamples * TEX_SIZE_ORTHO)      & 0xff;
	header[15] = ((subsamples * TEX_SIZE_ORTHO) >> 8) & 0xff;

	/* Write .tga header */
	fwrite(header, sizeof(unsigned char), sizeof(header) / sizeof(unsigned char), fp);

	/* Write pixel data  */
	for (int i = subsamples - 1; i >= 0; i--) {
		for (int y = TEX_SIZE_ORTHO - 1; y >= 0; y--) {
			for (int x = 0; x < TEX_SIZE_ORTHO; x++) {
				Dbl2 p = ortho->getPixel(i, Int2(x, y));
				fputc(0, fp);                            /* B */
				fputc((unsigned char)(p.y * 255.0), fp); /* G */
				fputc((unsigned char)(p.x * 255.0), fp); /* R */
				fputc(0, fp);                            /* A */
			}

			for (int x = 0; x < TEX_SIZE_DIAG; x++) {
				if (i < SUBSAMPLES_DIAG) {
					Dbl2 p = diag->getPixel(i, Int2(x, y));
					fputc(0, fp);                            /* B */
					fputc((unsigned char)(p.y * 255.0), fp); /* G */
					fputc((unsigned char)(p.x * 255.0), fp); /* R */
					fputc(0, fp);                            /* A */
				}
				else {
					fputc(0, fp);
					fputc(0, fp);
					fputc(0, fp);
					fputc(0, fp);
				}
			}
		}
	}
}

/* .raw File (R8G8 raw data) */
static void write_raw(AreaOrtho *ortho, AreaDiag *diag, FILE *fp, bool subsampling)
{
	int subsamples = subsampling ? SUBSAMPLES_ORTHO : 1;

	/* Write pixel data  */
	for (int i = 0; i < subsamples; i++) {
		for (int y = 0; y < TEX_SIZE_ORTHO; y++) {
			for (int x = 0; x < TEX_SIZE_ORTHO; x++) {
				Dbl2 p = ortho->getPixel(i, Int2(x, y));
				fputc((unsigned char)(p.x * 255.0), fp); /* R */
				fputc((unsigned char)(p.y * 255.0), fp); /* G */
			}

			for (int x = 0; x < TEX_SIZE_DIAG; x++) {
				if (i < SUBSAMPLES_DIAG) {
					Dbl2 p = diag->getPixel(i, Int2(x, y));
					fputc((unsigned char)(p.x * 255.0), fp); /* R */
					fputc((unsigned char)(p.y * 255.0), fp); /* G */
				}
				else {
					fputc(0, fp);
					fputc(0, fp);
				}
			}
		}
	}
}

static int generate_file(AreaOrtho *ortho, AreaDiag *diag, const char *path, bool subsampling, bool quantize, bool tga, bool raw)
{
	FILE *fp = fopen(path, tga ? "wb" : "w");

	if (!fp) {
		fprintf(stderr, "Unable to open file: %s\n", path);
		return 1;
	}

	// fprintf(stderr, "Generating %s\n", path);

	if (tga)
		write_tga(ortho, diag, fp, subsampling);
	else if (raw)
		write_raw(ortho, diag, fp, subsampling);
	else
		write_csource(ortho, diag, fp, subsampling, quantize);

	fclose(fp);

	return 0;
}

int main(int argc, char **argv)
{
	bool subsampling = false;
	bool quantize = false;
	bool tga = false;
	bool raw = false;
	bool compat = false;
	bool numeric = false;
	bool orig_u = false;
	bool help = false;
	char *outfile = NULL;
	int status = 0;

	for (int i = 1; i < argc; i++) {
		char *ptr = argv[i];
		if (*ptr++ == '-' && *ptr != '\0') {
			char c;
			while ((c = *ptr++) != '\0') {
				if (c == 's')
					subsampling = true;
				else if (c == 'q')
					quantize = true;
				else if (c == 't')
					tga = true;
				else if (c == 'r')
					raw = true;
				else if (c == 'c')
					compat = true;
				else if (c == 'n')
					numeric = true;
				else if (c == 'u')
					orig_u = true;
				else if (c == 'h')
					help = true;
				else {
					fprintf(stderr, "Unknown option: -%c\n", c);
					status = 1;
					break;
				}
			}
		}
		else if (outfile) {
			fprintf(stderr, "Too much file names: %s, %s\n", outfile, argv[i]);
			status = 1;
		}
		else
			outfile = argv[i];

		if (status != 0)
			break;
	}

	if (status == 0 && !help && !outfile) {
		fprintf(stderr, "File name was not specified.\n");
		status = 1;
	}

	if (status != 0 || help) {
		fprintf(stderr, "Usage: %s [OPTION]... OUTFILE\n", argv[0]);
		fprintf(stderr, "Options:\n");
		fprintf(stderr, "    -s    Calculate data for subpixel rendering\n");
		fprintf(stderr, "    -q    Quantize data to 256 levels\n");
		fprintf(stderr, "    -t    Write TGA image instead of C/C++ source\n");
		fprintf(stderr, "    -r    Write R8G8 raw image instead of C/C++ source\n");
		fprintf(stderr, "    -c    Generate compatible orthogonal data that subtexture size is 16\n");
		fprintf(stderr, "    -n    Numerically calculate diagonal data using brute force sampling\n");
		fprintf(stderr, "    -u    Process orthogonal / diagonal U patterns in older ways\n");
		fprintf(stderr, "    -h    Print this help and exit\n");
		fprintf(stderr, "File name OUTFILE usually should have an extension such as .c, .h, or .tga,\n");
		fprintf(stderr, "except for a special name '-' that means standard output.\n\n");
		fprintf(stderr, "Example:\n");
		fprintf(stderr, "  Generate TGA file exactly same as AreaTexDX10.tga bundled with the\n");
		fprintf(stderr, "  original implementation:\n\n");
		fprintf(stderr, "  $ smaa_areatex -stcnu AreaTexDX10.tga\n\n");
		return status;
	}

	AreaOrtho *ortho = new AreaOrtho(compat, orig_u);
	AreaDiag *diag = new AreaDiag(numeric, orig_u);

	/* Calculate areatex data */
	for (int i = 0; i < (subsampling ? SUBSAMPLES_ORTHO : 1); i++)
		ortho->areaTex(i);

	for (int i = 0; i < (subsampling ? SUBSAMPLES_DIAG : 1); i++)
		diag->areaTex(i);

	/* Generate .tga, .raw, or C/C++ source file, or write the data to stdout */
	if (strcmp(outfile, "-") != 0)
		status = generate_file(ortho, diag, outfile, subsampling, quantize, tga, raw);
	else if (tga)
		write_tga(ortho, diag, stdout, subsampling);
	else if (raw)
		write_raw(ortho, diag, stdout, subsampling);
	else
		write_csource(ortho, diag, stdout, subsampling, quantize);

	delete ortho;
	delete diag;

	return status;
}

/* smaa_areatex.cpp ends here */
