Home Reference Source

src/textures/smaa/SMAASearchImageData.js

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

/**
 * This dictionary returns which edges are active for a certain bilinear fetch: it's the reverse lookup of the bilinear
 * function.
 *
 * @type {Map<Number, Float32Array>}
 * @private
 */

const edges = new Map([

	[bilinear(0, 0, 0, 0), new Float32Array([0, 0, 0, 0])],
	[bilinear(0, 0, 0, 1), new Float32Array([0, 0, 0, 1])],
	[bilinear(0, 0, 1, 0), new Float32Array([0, 0, 1, 0])],
	[bilinear(0, 0, 1, 1), new Float32Array([0, 0, 1, 1])],

	[bilinear(0, 1, 0, 0), new Float32Array([0, 1, 0, 0])],
	[bilinear(0, 1, 0, 1), new Float32Array([0, 1, 0, 1])],
	[bilinear(0, 1, 1, 0), new Float32Array([0, 1, 1, 0])],
	[bilinear(0, 1, 1, 1), new Float32Array([0, 1, 1, 1])],

	[bilinear(1, 0, 0, 0), new Float32Array([1, 0, 0, 0])],
	[bilinear(1, 0, 0, 1), new Float32Array([1, 0, 0, 1])],
	[bilinear(1, 0, 1, 0), new Float32Array([1, 0, 1, 0])],
	[bilinear(1, 0, 1, 1), new Float32Array([1, 0, 1, 1])],

	[bilinear(1, 1, 0, 0), new Float32Array([1, 1, 0, 0])],
	[bilinear(1, 1, 0, 1), new Float32Array([1, 1, 0, 1])],
	[bilinear(1, 1, 1, 0), new Float32Array([1, 1, 1, 0])],
	[bilinear(1, 1, 1, 1), new Float32Array([1, 1, 1, 1])]

]);

/**
 * 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;

}

/**
 * Calculates the bilinear fetch for a certain edge combination.
 *
 *     e[0]       e[1]
 *
 *              x <-------- Sample Position: (-0.25, -0.125)
 *     e[2]       e[3] <--- Current Pixel [3]: (0.0, 0.0)
 *
 * @private
 * @param {Number} e0 - The edge combination.
 * @param {Number} e1 - The edge combination.
 * @param {Number} e2 - The edge combination.
 * @param {Number} e3 - The edge combination.
 * @return {Number} The interpolated value.
 */

function bilinear(e0, e1, e2, e3) {

	const a = lerp(e0, e1, 1.0 - 0.25);
	const b = lerp(e2, e3, 1.0 - 0.25);

	return lerp(a, b, 1.0 - 0.125);

}

/**
 * Computes the delta distance to add in the last step of searches to the left.
 *
 * @private
 * @param {Float32Array} left - The left edge combination.
 * @param {Float32Array} top - The top edge combination.
 * @return {Number} The left delta distance.
 */

function deltaLeft(left, top) {

	let d = 0;

	// If there is an edge, continue.
	if(top[3] === 1) {

		d += 1;

	}

	// If an edge was previously found, there's another edge and no crossing edges, continue.
	if(d === 1 && top[2] === 1 && left[1] !== 1 && left[3] !== 1) {

		d += 1;

	}

	return d;

}

/**
 * Computes the delta distance to add in the last step of searches to the right.
 *
 * @private
 * @param {Float32Array} left - The left edge combination.
 * @param {Float32Array} top - The top edge combination.
 * @return {Number} The right delta distance.
 */

function deltaRight(left, top) {

	let d = 0;

	// If there is an edge, and no crossing edges, continue.
	if(top[3] === 1 && left[1] !== 1 && left[3] !== 1) {

		d += 1;

	}

	// If an edge was previously found, there's another edge and there are no crossing edges.
	if(d === 1 && top[2] === 1 && left[0] !== 1 && left[2] !== 1) {

		d += 1;

	}

	return d;

}

/**
 * SMAA search image data.
 *
 * This image stores information about how many pixels the line search algorithm must advance in the last step.
 *
 * Based on the official python scripts:
 *  https://github.com/iryoku/smaa/tree/master/Scripts
 */

export class SMAASearchImageData {

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

	static generate() {

		const width = 66;
		const height = 33;
		const halfWidth = width / 2;

		const croppedWidth = 64;
		const croppedHeight = 16;

		const data = new Uint8ClampedArray(width * height);
		const croppedData = new Uint8ClampedArray(croppedWidth * croppedHeight * 4);

		// Calculate delta distances.
		for(let y = 0; y < height; ++y) {

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

				const s = 0.03125 * x;
				const t = 0.03125 * y;

				if(edges.has(s) && edges.has(t)) {

					const e1 = edges.get(s);
					const e2 = edges.get(t);

					const i = y * width + x;

					// Maximize the dynamic range to help the compression.
					data[i] = (127 * deltaLeft(e1, e2));
					data[i + halfWidth] = (127 * deltaRight(e1, e2));

				}

			}

		}

		// Crop the result to powers-of-two to make it BC4-friendly.
		for(let i = 0, y = height - croppedHeight; y < height; ++y) {

			for(let x = 0; x < croppedWidth; ++x, i += 4) {

				croppedData[i] = data[y * width + x];
				croppedData[i + 3] = 255;

			}

		}

		return new RawImageData(croppedWidth, croppedHeight, croppedData);

	}

}