Source: export/export_image.js

import {
    PerspectiveCamera, Scene, WebGLRenderer
} from '../../node_modules/three/build/three.module.js';


import { Reporter } from '../reporter/reporter.js';
import { LoadableComponent } from '../package/component/loadable_component.js';
import { checkPropTypes, UUIDRegex } from '../lib.js';
import { LoadingBase } from '../package/loader/loading_base.js';

import { Export } from './export.js';
import { Composer } from '../scene/composer.js';
import { EffectComposer } from '../../node_modules/three/examples/jsm/postprocessing/EffectComposer.js';

import { View } from '../view/view.js';
import { DefaultView } from '../view/default_view.js';

import html2canvas from '../../node_modules/html2canvas/dist/html2canvas.esm.js';

/** @class */
class ImageExporter { //extends Export {

    constructor(reporter, settings) {

        this.revision = 1;

        checkPropTypes(
            settings,
            {
                view: View || DefaultView,
            },
            {}
        );

        this._view = settings.view

    };

    _dataURIToBlob( dataURI ) {

        const binStr = window.atob( dataURI.split( ',' )[1] );
        const len = binStr.length;
        const arr = new Uint8Array( len );

        for ( let i = 0; i < len; i++ ) {
            arr[i] = binStr.charCodeAt( i );
        }

        return new window.Blob( [arr] );

    };

    _saveBlob(name, blob) {
        const link = document.createElement("a");
        link.download = name;

        const url = URL.createObjectURL(blob);
        link.href = url;

        link.onclick = () => {
            setTimeout(() => {
                URL.revokeObjectURL(url);
                link.removeAttribute("href");
            }, 500);
        };

        link.click();
    }

    _defaultFileName(ext) {

        const str = `${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}${ext}`;
        return str.replace(/\//g, '-').replace(/:/g, '.');

    };


    async export(width, height, {
        saveToFile,
        transparent,
        html,
        extension,
        fileName,
        returnType = "dataUrl",   // "dataUrl" or "blob"
        quality                   // optional, for jpg/webp
    } = {}) {

        if (width == null) width = 800;   // catches null + undefined
        if (height == null) height = 600;

        const originalAspect = this._view.perspectiveCamera.aspect;

        this._view.perspectiveCamera.aspect = width / height;
        this._view.perspectiveCamera.updateProjectionMatrix();

        const ext = (extension ?? "png").toLowerCase();
        const mime =
            ext === "jpg" || ext === "jpeg" ? "image/jpeg" :
            ext === "webp" ? "image/webp" :
            "image/png";

        if (transparent) this._view.renderer.setClearColor(0xffffff, 0);

        await this._view.update();

        this._view.renderer.setSize(width, height);
        this._view.composer.setSize(width, height);
        this._view.css2dRenderer.setSize(width, height);

        this._view.composer.render(this._view.scene, this._view.perspectiveCamera, null, false);

        let result = null;

        if (html) {
            const div = document.createElement("div");
            div.style.width = `${width}px`;
            div.style.height = `${height}px`;
            div.id = "htmlCanvas";
            div.style.zIndex = "-10";
            if (!transparent) div.style.background = "white";

            div.appendChild(this._view.renderer.domElement);
            div.appendChild(this._view.css2dRenderer.domElement);
            document.body.appendChild(div);

            try {
                this._view.composer.render(this._view.scene, this._view.perspectiveCamera, null, false);
                this._view.css2dRenderer.render(this._view.scene, this._view.perspectiveCamera, null, false);

                const mergedCanvas = await html2canvas(div, { backgroundColor: null });
                const blob = await this._canvasToBlob(mergedCanvas, mime, quality);

                if (saveToFile) {
                    const imageFileName = fileName ?? this._defaultFileName(`.${ext}`);
                    this._saveBlob(imageFileName, blob);
                }

                result = (returnType === "blob") ? blob : await this._blobToDataURL(blob);
            } finally {
                // restore the DOM even if html2canvas throws
                this._view.domElement.appendChild(this._view.renderer.domElement);
                this._view.domElement.appendChild(this._view.css2dRenderer.domElement);
                div.remove();
            }
        } else {
            const blob = await this._canvasToBlob(this._view.renderer.domElement, mime, quality);

            if (saveToFile) {
                const imageFileName = fileName ?? this._defaultFileName(`.${ext}`);
                this._saveBlob(imageFileName, blob);
            }

            result = (returnType === "blob") ? blob : await this._blobToDataURL(blob);
        }

        if (transparent) this._view.renderer.setClearColor(0xffffff, 0);

        this._view.perspectiveCamera.aspect = originalAspect;
        this._view.perspectiveCamera.updateProjectionMatrix();

        this._view.updateDOMElementDimensions();
        await this._view.update();

        return result;
    }


    // Helpers

    _canvasToBlob(canvas, mime = "image/png", quality) {
        return new Promise((resolve, reject) => {
            canvas.toBlob((blob) => {
                if (blob) return resolve(blob);
                // fallback for contexts where toBlob() yields null
                try {
                    const dataUrl = canvas.toDataURL(mime, quality);
                    resolve(this._dataURIToBlob(dataUrl));
                } catch (e) {
                    reject(new Error("canvas.toBlob() returned null"));
                }
            }, mime, quality);
        });
    }

    _blobToDataURL(blob) {
        return new Promise((resolve, reject) => {
            const r = new FileReader();
            r.onload = () => resolve(r.result);
            r.onerror = reject;
            r.readAsDataURL(blob);
        });
    }

}


export { ImageExporter }