Home Reference Source

src/textures/smaa/SMAAAreaImageData.js

import { RawImageData } from "../RawImageData.js";

/**
 * An 2D area, described by lower and upper bounds.
 *
 * @type {Float32Array[]}
 * @private
 */

const area = [
	new Float32Array(2),
	new Float32Array(2)
];

/**
 * The orthogonal texture size.
 *
 * @type {Number}
 * @private
 */

const ORTHOGONAL_SIZE = 16;

/**
 * The diagonal texture size.
 *
 * @type {Number}
 * @private
 */

const DIAGONAL_SIZE = 20;

/**
 * The number of samples for calculating areas in the diagonal textures.
 * Diagonal areas are calculated using brute force sampling.
 *
 * @type {Number}
 * @private
 */

const DIAGONAL_SAMPLES = 30;

/**
 * The maximum distance for smoothing U-shapes.
 *
 * @type {Number}
 * @private
 */

const SMOOTH_MAX_DISTANCE = 32;

/**
 * Subsampling offsets for orthogonal areas.
 *
 * @type {Float32Array}
 * @private
 */

const orthogonalSubsamplingOffsets = new Float32Array([
	0.0, -0.25, 0.25, -0.125, 0.125, -0.375, 0.375
]);

/**
 * Subsampling offset pairs for diagonal areas.
 *
 * @type {Float32Array[]}
 * @private
 */

const diagonalSubsamplingOffsets = [

	new Float32Array([0.0, 0.0]),
	new Float32Array([0.25, -0.25]),
	new Float32Array([-0.25, 0.25]),
	new Float32Array([0.125, -0.125]),
	new Float32Array([-0.125, 0.125])

];

/**
 * Orthogonal pattern positioning coordinates.
 *
 * Used for placing each pattern subtexture into a specific spot.
 *
 * @type {Uint8Array[]}
 * @private
 */

const orthogonalEdges = [

	new Uint8Array([0, 0]),
	new Uint8Array([3, 0]),
	new Uint8Array([0, 3]),
	new Uint8Array([3, 3]),

	new Uint8Array([1, 0]),
	new Uint8Array([4, 0]),
	new Uint8Array([1, 3]),
	new Uint8Array([4, 3]),

	new Uint8Array([0, 1]),
	new Uint8Array([3, 1]),
	new Uint8Array([0, 4]),
	new Uint8Array([3, 4]),

	new Uint8Array([1, 1]),
	new Uint8Array([4, 1]),
	new Uint8Array([1, 4]),
	new Uint8Array([4, 4])

];

/**
 * Diagonal pattern positioning coordinates.
 *
 * Used for placing each pattern subtexture into a specific spot.
 *
 * @type {Uint8Array[]}
 * @private
 */

const diagonalEdges = [

	new Uint8Array([0, 0]),
	new Uint8Array([1, 0]),
	new Uint8Array([0, 2]),
	new Uint8Array([1, 2]),

	new Uint8Array([2, 0]),
	new Uint8Array([3, 0]),
	new Uint8Array([2, 2]),
	new Uint8Array([3, 2]),

	new Uint8Array([0, 1]),
	new Uint8Array([1, 1]),
	new Uint8Array([0, 3]),
	new Uint8Array([1, 3]),

	new Uint8Array([2, 1]),
	new Uint8Array([3, 1]),
	new Uint8Array([2, 3]),
	new Uint8Array([3, 3])

];

/**
 * Linearly interpolates between two values.
 *
 * @private
 * @param {Number} a - The initial value.
 * @param {Number} b - The target value.
 * @param {Number} p - The interpolation value.
 * @return {Number} The interpolated value.
 */

function lerp(a, b, p) {

	return a + (b - a) * p;

}

/**
 * Clamps a value to the range [0, 1].
 *
 * @private
 * @param {Number} a - The value.
 * @return {Number} The saturated value.
 */

function saturate(a) {

	return Math.min(Math.max(a, 0.0), 1.0);

}

/**
 * A smoothing function for small U-patterns.
 *
 * @private
 * @param {Number} d - A smoothing factor.
 */

function smoothArea(d) {

	const a1 = area[0];
	const a2 = area[1];

	const b1X = Math.sqrt(a1[0] * 2.0) * 0.5;
	const b1Y = Math.sqrt(a1[1] * 2.0) * 0.5;
	const b2X = Math.sqrt(a2[0] * 2.0) * 0.5;
	const b2Y = Math.sqrt(a2[1] * 2.0) * 0.5;

	const p = saturate(d / SMOOTH_MAX_DISTANCE);

	a1[0] = lerp(b1X, a1[0], p);
	a1[1] = lerp(b1Y, a1[1], p);
	a2[0] = lerp(b2X, a2[0], p);
	a2[1] = lerp(b2Y, a2[1], p);

}

/**
 * Calculates the orthogonal area under the line p1 -> p2 for the pixels (x, x + 1).
 *
 * @private
 * @param {Number} p1X - The starting point of the line, X-component.
 * @param {Number} p1Y - The starting point of the line, Y-component.
 * @param {Number} p2X - The ending point of the line, X-component.
 * @param {Number} p2Y - The ending point of the line, Y-component.
 * @param {Number} x - The pixel index.
 * @param {Float32Array} result - A target tupel to store the area in.
 * @return {Float32Array} The area.
 */

function getOrthArea(p1X, p1Y, p2X, p2Y, x, result) {

	const dX = p2X - p1X;
	const dY = p2Y - p1Y;

	const x1 = x;
	const x2 = x + 1.0;

	const y1 = p1Y + dY * (x1 - p1X) / dX;
	const y2 = p1Y + dY * (x2 - p1X) / dX;

	// Check if x is inside the area.
	if((x1 >= p1X && x1 < p2X) || (x2 > p1X && x2 <= p2X)) {

		// Check if this is a trapezoid.
		if(Math.sign(y1) === Math.sign(y2) || Math.abs(y1) < 1e-4 || Math.abs(y2) < 1e-4) {

			const a = (y1 + y2) / 2.0;

			if(a < 0.0) {

				result[0] = Math.abs(a);
				result[1] = 0.0;

			} else {

				result[0] = 0.0;
				result[1] = Math.abs(a);

			}

		} else {

			// Two triangles.
			const t = -p1Y * dX / dY + p1X;
			const tInt = Math.trunc(t);

			const a1 = (t > p1X) ? y1 * (t - tInt) / 2.0 : 0.0;
			const a2 = (t < p2X) ? y2 * (1.0 - (t - tInt)) / 2.0 : 0.0;

			const a = (Math.abs(a1) > Math.abs(a2)) ? a1 : -a2;

			if(a < 0.0) {

				result[0] = Math.abs(a1);
				result[1] = Math.abs(a2);

			} else {

				result[0] = Math.abs(a2);
				result[1] = Math.abs(a1);

			}

		}

	} else {

		result[0] = 0.0;
		result[1] = 0.0;

	}

	return result;

}

/**
 * Calculates the area for a given pattern and distances to the left and to the
 * right, biased by an offset.
 *
 * @private
 * @param {Number} pattern - A pattern index.
 * @param {Number} left - The left distance.
 * @param {Number} right - The right distance.
 * @param {Number} offset - An offset.
 * @param {Float32Array} result - A target tupel to store the area in.
 * @return {Float32Array} The orthogonal area.
 */

function getOrthAreaForPattern(pattern, left, right, offset, result) {

	const a1 = area[0];
	const a2 = area[1];

	/* o1           |
	 *      .-------´
	 * o2   |
	 *
	 *      <---d--->
	 */

	const o1 = 0.5 + offset;
	const o2 = 0.5 + offset - 1.0;
	const d = left + right + 1;

	switch(pattern) {

		case 0: {

			//    ------

			result[0] = 0.0;
			result[1] = 0.0;

			break;

		}

		case 1: {

			/*   .------
			 *   |
			 *
			 * The offset is only applied to L patterns in the crossing edge side to make it converge with
			 * the unfiltered pattern 0. The pattern 0 must not be filtered to avoid artifacts.
			 */

			if(left <= right) {

				getOrthArea(0.0, o2, d / 2.0, 0.0, left, result);

			} else {

				result[0] = 0.0;
				result[1] = 0.0;

			}

			break;

		}

		case 2: {

			/*    ------.
			 *          |
			 */

			if(left >= right) {

				getOrthArea(d / 2.0, 0.0, d, o2, left, result);

			} else {

				result[0] = 0.0;
				result[1] = 0.0;

			}

			break;

		}

		case 3: {

			/*   .------.
			 *   |      |
			 */

			getOrthArea(0.0, o2, d / 2.0, 0.0, left, a1);
			getOrthArea(d / 2.0, 0.0, d, o2, left, a2);

			smoothArea(d, area);

			result[0] = a1[0] + a2[0];
			result[1] = a1[1] + a2[1];

			break;

		}

		case 4: {

			/*   |
			 *   `------
			 */

			if(left <= right) {

				getOrthArea(0.0, o1, d / 2.0, 0.0, left, result);

			} else {

				result[0] = 0.0;
				result[1] = 0.0;

			}

			break;

		}

		case 5: {

			/*   |
			 *   +------
			 *   |
			 */

			result[0] = 0.0;
			result[1] = 0.0;

			break;

		}

		case 6: {

			/*   |
			 *   `------.
			 *          |
			 *
			 * 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 an L pattern. To avoid discontinuities, the full offsetted Z
			 * revectorization is blended with partially offsetted L patterns.
			 */

			if(Math.abs(offset) > 0.0) {

				getOrthArea(0.0, o1, d, o2, left, a1);
				getOrthArea(0.0, o1, d / 2.0, 0.0, left, a2);
				getOrthArea(d / 2.0, 0.0, d, o2, left, result);

				a2[0] = a2[0] + result[0];
				a2[1] = a2[1] + result[1];

				result[0] = (a1[0] + a2[0]) / 2.0;
				result[1] = (a1[1] + a2[1]) / 2.0;

			} else {

				getOrthArea(0.0, o1, d, o2, left, result);

			}

			break;

		}

		case 7: {

			/*   |
			 *   +------.
			 *   |      |
			 */

			getOrthArea(0.0, o1, d, o2, left, result);

			break;

		}

		case 8: {

			/*          |
			 *    ------´
			 */

			if(left >= right) {

				getOrthArea(d / 2.0, 0.0, d, o1, left, result);

			} else {

				result[0] = 0.0;
				result[1] = 0.0;

			}

			break;

		}

		case 9: {

			/*          |
			 *   .------´
			 *   |
			 */

			if(Math.abs(offset) > 0.0) {

				getOrthArea(0.0, o2, d, o1, left, a1);
				getOrthArea(0.0, o2, d / 2.0, 0.0, left, a2);
				getOrthArea(d / 2.0, 0.0, d, o1, left, result);

				a2[0] = a2[0] + result[0];
				a2[1] = a2[1] + result[1];

				result[0] = (a1[0] + a2[0]) / 2.0;
				result[1] = (a1[1] + a2[1]) / 2.0;

			} else {

				getOrthArea(0.0, o2, d, o1, left, result);

			}

			break;

		}

		case 10: {

			/*          |
			 *    ------+
			 *          |
			 */

			result[0] = 0.0;
			result[1] = 0.0;

			break;

		}

		case 11: {

			/*          |
			 *   .------+
			 *   |      |
			 */

			getOrthArea(0.0, o2, d, o1, left, result);

			break;

		}

		case 12: {

			/*   |      |
			 *   `------´
			 */

			getOrthArea(0.0, o1, d / 2.0, 0.0, left, a1);
			getOrthArea(d / 2.0, 0.0, d, o1, left, a2);

			smoothArea(d, area);

			result[0] = a1[0] + a2[0];
			result[1] = a1[1] + a2[1];

			break;

		}

		case 13: {

			/*   |      |
			 *   +------´
			 *   |
			 */

			getOrthArea(0.0, o2, d, o1, left, result);

			break;

		}

		case 14: {

			/*   |      |
			 *   `------+
			 *          |
			 */

			getOrthArea(0.0, o1, d, o2, left, result);

			break;

		}

		case 15: {

			/*   |      |
			 *   +------+
			 *   |      |
			 */

			result[0] = 0.0;
			result[1] = 0.0;

			break;

		}

	}

	return result;

}

/**
 * Determines whether the given pixel is inside the specified area.
 *
 * @private
 * @param {Number} a1X - The lower bounds of the area, X-component.
 * @param {Number} a1Y - The lower bounds of the area, Y-component.
 * @param {Number} a2X - The upper bounds of the area, X-component.
 * @param {Number} a2Y - The upper bounds of the area, Y-component.
 * @param {Number} x - The X-coordinate.
 * @param {Number} y - The Y-coordinate.
 * @return {Boolean} Whether the pixel lies inside the area.
 */

function isInsideArea(a1X, a1Y, a2X, a2Y, x, y) {

	let result = (a1X === a2X && a1Y === a2Y);

	if(!result) {

		const xm = (a1X + a2X) / 2.0;
		const ym = (a1Y + a2Y) / 2.0;

		const a = a2Y - a1Y;
		const b = a1X - a2X;

		const c = a * (x - xm) + b * (y - ym);

		result = (c > 0.0);

	}

	return result;

}

/**
 * Calculates the diagonal area under the line p1 -> p2 for the pixel p using brute force sampling.
 *
 * @private
 * @param {Number} a1X - The lower bounds of the area, X-component.
 * @param {Number} a1Y - The lower bounds of the area, Y-component.
 * @param {Number} a2X - The upper bounds of the area, X-component.
 * @param {Number} a2Y - The upper bounds of the area, Y-component.
 * @param {Number} pX - The X-coordinate.
 * @param {Number} pY - The Y-coordinate.
 * @return {Number} The amount of pixels inside the area relative to the total amount of sampled pixels.
 */

function getDiagAreaForPixel(a1X, a1Y, a2X, a2Y, pX, pY) {

	let n = 0;

	for(let y = 0; y < DIAGONAL_SAMPLES; ++y) {

		for(let x = 0; x < DIAGONAL_SAMPLES; ++x) {

			const offsetX = x / (DIAGONAL_SAMPLES - 1.0);
			const offsetY = y / (DIAGONAL_SAMPLES - 1.0);

			if(isInsideArea(a1X, a1Y, a2X, a2Y, pX + offsetX, pY + offsetY)) {

				++n;

			}

		}

	}

	return n / (DIAGONAL_SAMPLES * DIAGONAL_SAMPLES);

}

/**
 * Calculates the area under the line p1 -> p2. This includes the pixel and its opposite.
 *
 * @private
 * @param {Number} pattern - A pattern index.
 * @param {Number} a1X - The lower bounds of the area, X-component.
 * @param {Number} a1Y - The lower bounds of the area, Y-component.
 * @param {Number} a2X - The upper bounds of the area, X-component.
 * @param {Number} a2Y - The upper bounds of the area, Y-component.
 * @param {Number} left - The left distance.
 * @param {Float32Array} offset - An offset.
 * @param {Float32Array} result - A target tupel to store the area in.
 * @return {Float32Array} The area.
 */

function getDiagArea(pattern, a1X, a1Y, a2X, a2Y, left, offset, result) {

	const e = diagonalEdges[pattern];
	const e1 = e[0];
	const e2 = e[1];

	if(e1 > 0) {

		a1X += offset[0];
		a1Y += offset[1];

	}

	if(e2 > 0) {

		a2X += offset[0];
		a2Y += offset[1];

	}

	result[0] = 1.0 - getDiagAreaForPixel(a1X, a1Y, a2X, a2Y, 1.0 + left, 0.0 + left);
	result[1] = getDiagAreaForPixel(a1X, a1Y, a2X, a2Y, 1.0 + left, 1.0 + left);

	return result;

}

/**
 * Calculates the area for a given pattern and distances to the left and right, biased by an offset.
 *
 * @private
 * @param {Number} pattern - A pattern index.
 * @param {Number} left - The left distance.
 * @param {Number} right - The right distance.
 * @param {Float32Array} offset - An offset.
 * @param {Float32Array} result - A target tupel to store the area in.
 * @return {Float32Array} The orthogonal area.
 */

function getDiagAreaForPattern(pattern, left, right, offset, result) {

	const a1 = area[0];
	const a2 = area[1];

	const d = left + right + 1;

	/* There is some Black Magic involved in the 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 the adjacent pattern is
	 * unknown. Therefore, a blend of both possibilities is computed.
	 */

	switch(pattern) {

		case 0: {

			/*         .-´
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 */

			// First possibility.
			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 1.0 + d, left, offset, a1);

			// Second possibility.
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			// Blend both possibilities together.
			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 1: {

			/*         .-´
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 0.0, 0.0 + d, 0.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 2: {

			/*         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 */

			getDiagArea(pattern, 0.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 3: {

			/*
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, result);

			break;

		}

		case 4: {

			/*         .-´
			 *       .-´
			 *     .-´
			 * ----´
			 */

			getDiagArea(pattern, 1.0, 1.0, 0.0 + d, 0.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 5: {

			/*         .-´
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 1.0, 0.0 + d, 0.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 6: {

			/*         .----
			 *       .-´
			 *     .-´
			 * ----´
			 */

			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 0.0 + d, left, offset, result);

			break;

		}

		case 7: {

			/*         .----
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 0.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 8: {

			/*         |
			 *         |
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 */

			getDiagArea(pattern, 0.0, 0.0, 1.0 + d, 1.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 1.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 9: {

			/*         |
			 *         |
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 1.0 + d, left, offset, result);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 1.0 + d, left, offset, result);

			break;

		}

		case 10: {

			/*         |
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   ´
			 */

			getDiagArea(pattern, 0.0, 0.0, 1.0 + d, 1.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 11: {

			/*         |
			 *         .----
			 *       .-´
			 *     .-´
			 *   .-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 1.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 12: {

			/*         |
			 *         |
			 *       .-´
			 *     .-´
			 * ----´
			 */

			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 1.0 + d, left, offset, result);

			break;

		}

		case 13: {

			/*         |
			 *         |
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 1.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 1.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 14: {

			/*         |
			 *         .----
			 *       .-´
			 *     .-´
			 * ----´
			 */

			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 1.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

		case 15: {

			/*         |
			 *         .----
			 *       .-´
			 *     .-´
			 * --.-´
			 *   |
			 *   |
			 */

			getDiagArea(pattern, 1.0, 1.0, 1.0 + d, 1.0 + d, left, offset, a1);
			getDiagArea(pattern, 1.0, 0.0, 1.0 + d, 0.0 + d, left, offset, a2);

			result[0] = (a1[0] + a2[0]) / 2.0;
			result[1] = (a1[1] + a2[1]) / 2.0;

			break;

		}

	}

	return result;

}

/**
 * Calculates orthogonal or diagonal patterns for a given offset.
 *
 * @private
 * @param {RawImageData[]} patterns - The patterns to assemble.
 * @param {Number|Float32Array} offset - A pattern offset. Diagonal offsets are pairs.
 * @param {Boolean} orthogonal - Whether the patterns are orthogonal or diagonal.
 */

function generatePatterns(patterns, offset, orthogonal) {

	const result = new Float32Array(2);

	for(let i = 0, l = patterns.length; i < l; ++i) {

		const pattern = patterns[i];
		const data = pattern.data;
		const size = pattern.width;

		for(let y = 0; y < size; ++y) {

			for(let x = 0; x < size; ++x) {

				if(orthogonal) {

					getOrthAreaForPattern(i, x, y, offset, result);

				} else {

					getDiagAreaForPattern(i, x, y, offset, result);

				}

				const c = (y * size + x) * 2;
				data[c] = result[0] * 255;
				data[c + 1] = result[1] * 255;

			}

		}

	}

}

/**
 * Assembles orthogonal or diagonal patterns into the final area image.
 *
 * @private
 * @param {Number} baseX - The base X-position.
 * @param {Number} baseY - The base Y-position.
 * @param {RawImageData[]} patterns - The patterns to assemble.
 * @param {Uint8Array[]} edges - Edge coordinate pairs, used for positioning.
 * @param {Number} size - The pattern size.
 * @param {Boolean} orthogonal - Whether the patterns are orthogonal or diagonal.
 * @param {RawImageData} target - The target image data.
 */

function assemble(baseX, baseY, patterns, edges, size, orthogonal, target) {

	const dstData = target.data;
	const dstWidth = target.width;

	for(let i = 0, l = patterns.length; i < l; ++i) {

		const edge = edges[i];
		const pattern = patterns[i];

		const srcData = pattern.data;
		const srcWidth = pattern.width;

		for(let y = 0; y < size; ++y) {

			for(let x = 0; x < size; ++x) {

				const pX = edge[0] * size + baseX + x;
				const pY = edge[1] * size + baseY + y;

				const c = (pY * dstWidth + pX) * 4;

				// The texture coords of orthogonal patterns are compressed quadratically to reach longer distances.
				const d = orthogonal ? ((y * y * srcWidth + x * x) * 2) : ((y * srcWidth + x) * 2);

				dstData[c] = srcData[d];
				dstData[c + 1] = srcData[d + 1];
				dstData[c + 2] = 0;
				dstData[c + 3] = 255;

			}

		}

	}

}

/**
 * SMAA area image data.
 *
 * This texture allows to obtain the area for a certain pattern and distances to the left and right of identified lines.
 *
 * Based on the official python scripts:
 *  https://github.com/iryoku/smaa/tree/master/Scripts
 */

export class SMAAAreaImageData {

	/**
	 * Creates a new area image.
	 *
	 * @return {RawImageData} The generated image data.
	 */

	static generate() {

		const width = 2 * 5 * ORTHOGONAL_SIZE;
		const height = orthogonalSubsamplingOffsets.length * 5 * ORTHOGONAL_SIZE;

		const data = new Uint8ClampedArray(width * height * 4);
		const result = new RawImageData(width, height, data);

		const orthPatternSize = Math.pow(ORTHOGONAL_SIZE - 1, 2) + 1;
		const diagPatternSize = DIAGONAL_SIZE;

		const orthogonalPatterns = [];
		const diagonalPatterns = [];

		for(let i = 3, l = data.length; i < l; i += 4) {

			data[i] = 255;

		}

		// Prepare 16 image data sets for the orthogonal and diagonal subtextures.
		for(let i = 0; i < 16; ++i) {

			orthogonalPatterns.push(new RawImageData(
				orthPatternSize, orthPatternSize,
				new Uint8ClampedArray(orthPatternSize * orthPatternSize * 2),
				2
			));

			diagonalPatterns.push(new RawImageData(
				diagPatternSize, diagPatternSize,
				new Uint8ClampedArray(diagPatternSize * diagPatternSize * 2),
				2
			));

		}

		for(let i = 0, l = orthogonalSubsamplingOffsets.length; i < l; ++i) {

			// Generate 16 orthogonal patterns for each offset.
			generatePatterns(orthogonalPatterns, orthogonalSubsamplingOffsets[i], true);

			// Assemble the orthogonal patterns and place them on the left side.
			assemble(
				0,
				5 * ORTHOGONAL_SIZE * i,
				orthogonalPatterns,
				orthogonalEdges,
				ORTHOGONAL_SIZE,
				true,
				result
			);

		}

		for(let i = 0, l = diagonalSubsamplingOffsets.length; i < l; ++i) {

			// Generate 16 diagonal patterns for each offset.
			generatePatterns(diagonalPatterns, diagonalSubsamplingOffsets[i], false);

			// Assemble the diagonal patterns and place them on the right side.
			assemble(
				5 * ORTHOGONAL_SIZE,
				4 * DIAGONAL_SIZE * i,
				diagonalPatterns,
				diagonalEdges,
				DIAGONAL_SIZE,
				false,
				result
			);

		}

		return result;

	}

}