Source: HairToneAnalyzer.js

import Globals from "./Globals";
import { MountFileWithProgress } from "./Utility/MountFileWithProgress";

let resource_loading = null;

export class HairToneAnalyzer {
    static async Create(modulesManager, onProgress) {
        const bsc = modulesManager.modules.visage.module;
        const bscAnalyzer = bsc.GetHairToneAnalysis();

        if (!resource_loading) {
            // NOTE(Straza) byteLen added because if the server uses compression (gzip),
            // content-length will not be correct
            // there is no standard header for uncompressed size
            const resources = [
                {
                    name: "hs_c.bin",
                    path: Globals.pathToAssets + "Assets/Plugins/hs_c.bin",
                    byteLen: 1236,
                },
                {
                    name: "hs_m.js.json",
                    path: Globals.pathToAssets + "Assets/Plugins/hs_m.js.json",
                    byteLen: 53392,
                },
                {
                    name: "hs_m.js.bin",
                    path: Globals.pathToAssets + "Assets/Plugins/hs_m.js.bin",
                    byteLen: 4297568,
                },
            ];

            const curentprogresses = resources.reduce((acc, x) => {
                acc[x.name] = {
                    contentLength: x.byteLen,
                    receivedLength: 0,
                    progress: 0,
                    done: false,
                };
                return acc;
            }, {});

            const total = resources
                .map((k) => k.byteLen)
                .reduce((partialSum, a) => partialSum + a, 0);

            const start = () => { };
            const progress = (name) => (x) => {
                if (!onProgress) return;

                curentprogresses[name] = x;

                const keys = Object.keys(curentprogresses);
                const done = keys.every((k) => curentprogresses[k].done);

                let received = 0;

                if (done) {
                    received = total;
                } else {
                    received = keys
                        .map((k) => curentprogresses[k].receivedLength)
                        .reduce((partialSum, a) => partialSum + a, 0);
                }

                onProgress({
                    contentLength: total,
                    receivedLength: received,
                    progress: received / total,
                    done,
                });
            };

            const resource_promises = resources.map((r) =>
                MountFileWithProgress(
                    bsc.FS,
                    r.name,
                    r.path,
                    progress(r.name),
                    start,
                    r.byteLen,
                ),
            );

            resource_loading = Promise.all(resource_promises).catch(
                () => (resource_loading = null),
            );
        }
        await resource_loading;

        return new HairToneAnalyzer(bscAnalyzer, bsc);
    }

    constructor(bscAnalyzer, bsc) {
        this.bscAnalyzer = bscAnalyzer;
        this.bsc = bsc;
    }

    /**
     *@callback HairToneCallback
     *@param {Object} HairToneAnalyzerResult - Analyzed hair tone
     *@param {HairToneEstimates} HairToneAnalyzerResult.estimates - Object containing estimates of hue, saturation and lightness values.
     *@param {number} HairToneAnalyzerResult.frameIndex -  Index of the frame that has been analyzed.
     *@param { "OFF"|"OK"|"RECOVERING"|"INIT" } HairToneAnalyzerResult.status - Indicates tracking status. If no hair has been detected, returned status will be set to "INIT".
     */

    /**
     * Initiates the hair tone analysis. When a frame has been analyzed a call to the {@link HairToneCallback}
     * function is issued, with the analysis results passed as the argument.
     * <br/>
     * @param {HairToneCallback} HairToneCallback - callback which will be called every time a new frame is analyzed
     */
    StartAnalysis(HairToneCallback) {
        this.bscAnalyzer.SetCallback(HairToneCallback);
        this.bscAnalyzer.StartAnalysis();
    }

    /**
     * Stops the hair tone analysis and stops issuing calls to the callback given as a argument to the
     * {@link StartAnalysis} function.
     * <br/>
     */
    StopAnalysis() {
        this.bscAnalyzer.StopAnalysis();
    }

    /**
     * Resets the internal state of the hair tone analyzer. This is useful when switching between frames of different people.
     */
    Reset() {
        this.bscAnalyzer.Reset();
    }
}