Source: visualize/iv/ImageProcessor.js

/*
 * License information at https://github.com/Caltech-IPAC/firefly/blob/master/License.txt
 */

import {isString} from 'lodash';
import {loadImage, loadCancelableImage, requestIdleCallback} from '../../util/WebUtil.js';

/**
 * @global
 * @public
 * @typedef {Object} ImageContainer
 * Always contains key image and then anything else to specify the state
 *
 * @prop {Object} image
 * @prop {Object} src
 * @prop {String} dataUrl
 * @prop {Object} tileAttributes
 * @prop {Object} canCompress
 *
 */

/**
 *
 * @return {Promise} promise with new imageData
 */
/**
 *
 * @param {ImageContainer|Object|String} imageData the current image data or a html image object or the source of the
 *                                       of an image to load
 * @param {Object} nextTileAttributes next state to process image
 * @param shouldProcess
 * @param processor
 * @return {Promise}
 */
export function retrieveAndProcessImage(imageData, nextTileAttributes, shouldProcess, processor) {

    if (!imageData) {
        return Promise.resolve(imageData);
    }
    else if (isString(imageData)) {
        let {promise, cancelImageLoad}= loadCancelableImage(imageData);

         promise=  promise.then( (image) =>
             modifyImage({image, tileAttributes:nextTileAttributes},nextTileAttributes,true, shouldProcess, processor));
         return convertToReturn(promise, cancelImageLoad);
    }
    else if (imageData instanceof HTMLImageElement) {
        const v= modifyImage({image:imageData, tileAttributes:nextTileAttributes}, nextTileAttributes, true, shouldProcess, processor);
        return convertToReturn(v);

    }
    else {
        if (imageData.image) {
            // console.log('Image From Cache');
            const v= modifyImage(imageData, nextTileAttributes,false, shouldProcess, processor);
            return convertToReturn(v);

        }
        else if (imageData.dataUrl) {
            let {promise, cancelImageLoad}= loadCancelableImage(imageData.dataUrl);
            promise= promise.then( (image) => modifyImage({image, tileAttributes:imageData.tileAttributes},
                                                nextTileAttributes,false, shouldProcess, processor));
            convertToReturn(promise, cancelImageLoad);
        }
    }
    return convertToReturn(
        Promise.reject(new Error('could note identify imageData: not string, HTMLImageElement, imageData.image, or imageData.dataUrl')));
}


function convertToReturn(obj, cancelImageLoad= undefined) {
    if (obj.then) return {promise:obj,cancelImageLoad};
    else if (obj.promise) return obj;
    else throw new Error('unexpected return object in ImageProcessor.retrieveAndProcessImage');
}


/**
 *
 * @param {ImageData} imageData
 * @param {Object} nextTileAttributes
 * @param {boolean} newData
 * @param shouldProcess
 * @param processor
 * @return {ImageData}
 */
function modifyImage(imageData, nextTileAttributes, newData, shouldProcess, processor) {
    if (processor && shouldProcess && shouldProcess(imageData.image, newData, imageData.tileAttributes, nextTileAttributes) ) {
        // console.log('modifyImage');
        return new Promise( (resolve, reject) => {
            requestIdleCallback( () => {
                const results= processImage(imageData.image, nextTileAttributes, processor);
                resolve(
                    {
                        image: results.canvas,
                        compressible: results.compressible,
                        tileAttributes: nextTileAttributes,
                    } );
            }, {timeout:100});
        });
    }
    else {
        return Promise.resolve(imageData);
    }
}


/**
 *
 * @param imageOrCanvas
 * @param tileAttributes
 * @param processor
 * @return {ImageData}
 */
function processImage(imageOrCanvas, tileAttributes, processor) {
    let canvas;
    let ctx;
    if (imageOrCanvas instanceof HTMLImageElement) {
        canvas = document.createElement('canvas');
        const image= imageOrCanvas;
        ctx = canvas.getContext('2d');
        canvas.width = image.width;
        canvas.height = image.height;
        ctx.drawImage(image, 0,0);
    }
    else {
        canvas= imageOrCanvas;
        ctx = canvas.getContext('2d');
    }
    const imageData= ctx.getImageData(0,0,canvas.width,canvas.height);
    const newImageData= processor(imageData);
    ctx.putImageData(newImageData.imageData, 0, 0);
    return {canvas, compressible:newImageData.compressible};
}