Home Reference Source

src/loaders/SMAAImageLoader.js

import { Loader, LoadingManager } from "three";
import { RawImageData } from "../images/RawImageData";
import workerProgram from "../images/smaa/worker.tmp";

/**
 * Generates the SMAA data images.
 *
 * @private
 * @param {Boolean} [disableCache=false] - Determines whether the generated image data should be cached.
 * @return {Promise} A promise that returns the search image and area image as a data URL pair.
 */

function generate(disableCache = false) {

	const workerURL = URL.createObjectURL(new Blob([workerProgram], { type: "text/javascript" }));
	const worker = new Worker(workerURL);

	return new Promise((resolve, reject) => {

		worker.addEventListener("error", (event) => reject(event.error));
		worker.addEventListener("message", (event) => {

			const searchImageData = RawImageData.from(event.data.searchImageData);
			const areaImageData = RawImageData.from(event.data.areaImageData);

			const urls = [
				searchImageData.toCanvas().toDataURL(),
				areaImageData.toCanvas().toDataURL()
			];

			if(!disableCache && window.localStorage !== undefined) {

				localStorage.setItem("smaa-search", urls[0]);
				localStorage.setItem("smaa-area", urls[1]);

			}

			URL.revokeObjectURL(workerURL);
			resolve(urls);

		});

		worker.postMessage(null);

	});

}

/**
 * An SMAA image loader.
 *
 * This loader uses a worker thread to generate the search and area images. The
 * Generated data URLs will be cached using localStorage, if available. To
 * disable caching, please refer to {@link SMAAImageLoader.disableCache}.
 *
 * @experimental Added for testing, API might change in patch or minor releases. Requires three >= r108.
 */

export class SMAAImageLoader extends Loader {

	/**
	 * Constructs a new SMAA image loader.
	 *
	 * @param {LoadingManager} [manager] - A loading manager.
	 */

	constructor(manager) {

		super(manager);

		/**
		 * Indicates whether data image caching is disabled.
		 *
		 * @type {Boolean}
		 */

		this.disableCache = false;

	}

	/**
	 * Loads the SMAA data images.
	 *
	 * @param {Function} [onLoad] - A function to call when the loading process is done.
	 * @param {Function} [onError] - A function to call when an error occurs.
	 * @return {Promise<Image[]>} A promise that returns the search image and area image as a pair.
	 */

	load(onLoad = () => {}, onError = () => {}) {

		// Conform to the signature (url, onLoad, onProgress, onError).
		if(arguments.length === 4) {

			onLoad = arguments[1];
			onError = arguments[3];

		} else if(arguments.length === 3 || typeof arguments[0] !== "function") {

			onLoad = arguments[1];
			onError = () => {};

		}

		const externalManager = this.manager;
		const internalManager = new LoadingManager();

		externalManager.itemStart("smaa-search");
		externalManager.itemStart("smaa-area");
		internalManager.itemStart("smaa-search");
		internalManager.itemStart("smaa-area");

		return new Promise((resolve, reject) => {

			const cachedURLs = (!this.disableCache && window.localStorage !== undefined) ? [
				localStorage.getItem("smaa-search"),
				localStorage.getItem("smaa-area")
			] : [null, null];

			const promise = (cachedURLs[0] !== null && cachedURLs[1] !== null) ?
				Promise.resolve(cachedURLs) : generate(this.disableCache);

			promise.then((urls) => {

				const result = [new Image(), new Image()];

				internalManager.onLoad = () => {

					onLoad(result);
					resolve(result);

				};

				result[0].addEventListener("load", () => {

					externalManager.itemEnd("smaa-search");
					internalManager.itemEnd("smaa-search");

				});

				result[1].addEventListener("load", () => {

					externalManager.itemEnd("smaa-area");
					internalManager.itemEnd("smaa-area");

				});

				result[0].src = urls[0];
				result[1].src = urls[1];

			}).catch((error) => {

				externalManager.itemError("smaa-search");
				externalManager.itemError("smaa-area");

				onError(error);
				reject(error);

			});

		});

	}

}