Source: image-provider.js

"use strict";

/** Decoder for HEIF files. Glues HEIFReader and HevcDecoder together.
 * @constructor
 * @param {HEIFReader} heifReader HEIF file reader object to read hevc encoded data.
 * @param {HevcDecoder} hevcDecoder HEVC decoder object to decode hevc encoded data. */
function ImageProvider (heifReader, hevcDecoder) {

    var _name = "ImageProvider";

    var self = this;

    this._decoderParameters = null;
    this._heifReader = heifReader;
    this._hevcDecoder = hevcDecoder;

    function _appendBuffer(buffer1, buffer2) {
        var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
        tmp.set(new Uint8Array(buffer1), 0);
        tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
        return tmp.buffer;
    }

    // **** PUBLIC API BEGINS ****

    /** Payload structure returned to the caller as a callback parameter.
     *  @constructor
     *  @param {boolean} success True if decoding was successful.
     *  @param {Uint8Array} frames Array of frame data.
     *  @param {number} displayWidth Max width of all frames.
     *  @param {number} displayHeight Max height of all frames. */
    function Payload (success, frames, displayWidth, displayHeight) {
        this.success = success;
        this.frames = frames;
        this.displayWidth = displayWidth;
        this.displayHeight = displayHeight;
    }

    /** RequestContext is used to queue data requests and is pushed to HevcDecoder.
     *  @constructor
     *  @param {function} callback Called when all items (frames) are decoded. The result is passed a as parameter.
     *  @param {Array.<number>} itemIds Array of item id's to be decoded.
     *  @param {Uint8Array} stream The data stream as Uint8Array.
     *  @param dependencies Item id map of dependency/reference frames that are not to be displayed.
     *  @param {Payload} payload The object returned to the caller. */
    function RequestContext (callback, itemIds, stream, dependencies, payload) {
        this.callback = callback;
        this.itemIds = itemIds;
        this.currentFrameIndex = 0;
        this.stream = stream;
        this.dependencies = dependencies;
        this.payload = payload;
    };

    /** Request decoded image data for given item id's.
     *  @param {number} contextId Id of the context.
     *  @param {Array.<number>} itemIds Array of item id's to be decoded.
     *  @param {function} callback Callback function that receives the payload as a parameter. */
    this.requestImageData = function (contextId, itemIds, callback) {

        console.log(_name + ": REQUEST IMAGE DATA: " + itemIds);

        if (itemIds.constructor !== Array) {
            itemIds = [itemIds];
        }

        if (itemIds.length < 1) {
            return;
        }

        var stream = new Uint8Array();
        var decodeIds = [];
        var dependencies = {};
        var displayDependencies = {};
        for (var i = 0; i < itemIds.length; i++) {
            var refs = self._heifReader.getItemDecodeDependencies(contextId, itemIds[i]);
            for (var j = 0; j < refs.length; j++) {
                if (refs[j] !== itemIds[i]) {
                    // Add dependency data to the stream if the id is not already listed in dependencies
                    if(!(refs[j] in dependencies) && !(refs[j] in displayDependencies)) {
                        dependencies[refs[j]] = true;
                        stream = _appendBuffer(stream, new Uint8Array(self._heifReader.getItemDataWithDecoderParameters(contextId, refs[j])));
                        decodeIds.push(refs[j]);
                    }
                } else {
                    // Item has itself as dependency => we want to display that item despite being a dependency
                    displayDependencies[refs[j]] = true;
                }
            }
        }

        // Add data of the actual items to the stream (that already contains dependency data)
        for (i = 0; i < itemIds.length; i++) {
            stream = _appendBuffer(stream, new Uint8Array(self._heifReader.getItemDataWithDecoderParameters(contextId, itemIds[i])));
            decodeIds.push(itemIds[i]);
        }

        var payload = new Payload(true, [], 0, 0);
        self._hevcDecoder.pushRequestContext(new RequestContext(callback, decodeIds, stream, dependencies, payload));
        self._hevcDecoder.decode();
    };

    this._getDisplayItems = function (fileInfo, contextId) {
        if (fileInfo.getContextType(contextId) === "trak") {
            return self._heifReader.getItemListByType(contextId, "display");
        } else {
            return self._heifReader.getItemListByType(contextId, "master");
        }
    };

    /** A convenience method to get display height of given context.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @param {number} contextId Id of the context.
     *  @return {number} Display height. */
    this.getDisplayHeight = function (fileInfo, contextId) {
        var items = self._getDisplayItems(fileInfo, contextId);
        var maxHeight = 0;
        for (var i = 0; i < items.length; i++) {
            var height = self._heifReader.getItemHeight(contextId, items[i]);
            if (height > maxHeight) {
                maxHeight = height;
            }
        }
        return maxHeight;
    };

    /** A convenience method to get display width of given context.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @param {number} contextId Id of the context.
     *  @return {number} Display width. */
    this.getDisplayWidth = function (fileInfo, contextId) {
        var items = self._getDisplayItems(fileInfo, contextId);
        var maxWidth = 0;
        for (var i = 0; i < items.length; i++) {
            var width = self._heifReader.getItemWidth(contextId, items[i]);
            if (width > maxWidth) {
                maxWidth = width;
            }
        }
        return maxWidth;
    };

    /** A convenience function to get item id by thumbnail id.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @param {number} contextId Id of the context.
     *  @param {number} thumbnailId Id of the thumbnail.
     *  @return {number} Id of the corresponding item. */
    this.getItemIdByThumbnailId = function (fileInfo, contextId, thumbnailId) {
        // In Trak thumbnails and master images are on separate tracks with corresponding ids.
        if (fileInfo.trackProperties && fileInfo.trackProperties.length) {
            return thumbnailId;
        } else if (fileInfo.fileFeature.hasRootLevelMetaBox) {
            var refs = self._heifReader.getReferencedFromItemListByType(contextId, thumbnailId, "thmb");
            // For now, return the first id
            if (refs && refs.length) {
                return refs[0];
            }
        }
        return null;
    };

    /** A convenience method to get thumbnail context id or null.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @return {number} Thumbnail contextID or null if no thumbanails are present. */
    this.getThumbnailContextId = function (fileInfo) {
        if (fileInfo.trackProperties && fileInfo.trackProperties.length) {
            for (var i in fileInfo.trackProperties) {
                if (fileInfo.trackProperties[i].trackFeature.isThumbnailImageSequence === true) {
                    return fileInfo.trackProperties[i].trackId;
                }
            }
        }

        if (fileInfo.rootLevelMetaBoxProperties) {
            if (fileInfo.rootLevelMetaBoxProperties.metaBoxFeature.hasThumbnails === true) {
                return fileInfo.rootLevelMetaBoxProperties.contextId;
            }
        }

        return null;
    };

    /** A convenience method to get master context id or null.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @return {number} Master contextID or null if no master context is present. */
    this.getMasterContextId = function (fileInfo) {
        if (fileInfo.trackProperties && fileInfo.trackProperties.length) {
            for (var i in fileInfo.trackProperties) {
                if (fileInfo.trackProperties[i].trackFeature.isMasterImageSequence === true) {
                    return fileInfo.trackProperties[i].trackId;
                }
            }
        }

        if (fileInfo.rootLevelMetaBoxProperties) {
            return fileInfo.rootLevelMetaBoxProperties.contextId;
        }

        return null;
    };

    /** A convenience function to get thumbnail id by item id.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @param {number} contextId Id of the context.
     *  @param {number} itemId Id of the item.
     *  @return {number} Id of the corresponding thumbnail or null if there is no thumbnail. */
    this.getThumbnailIdByItemId = function (fileInfo, contextId, itemId) {
        // In Trak thumbnails and master images are on separate tracks with corresponding ids.
        if (fileInfo.trackProperties && fileInfo.trackProperties.length) {
            return itemId;
        } else if (fileInfo.fileFeature.hasRootLevelMetaBox) {
            var refs = self._heifReader.getReferencedToItemListByType(contextId, itemId, "thmb");
            // For now, return the first id
            if (refs && refs.length) {
                return refs[0];
            }
        }
        return null;
    };

    /** A convenience function to get master ids.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @return {Array.<number>} Array of master item IDs. */
    this.getMasterIds = function (fileInfo) {
        var ids = null;
        var masterContextId = self.getMasterContextId(fileInfo);
        if (fileInfo.trackProperties && fileInfo.trackProperties.length) {
            ids = [];
            for (var i in fileInfo.trackProperties) {
                if (fileInfo.trackProperties[i].trackId === masterContextId) {
                    var sampleProperties = fileInfo.trackProperties[i].sampleProperties;
                    for (var j in sampleProperties) {
                        if (sampleProperties.hasOwnProperty(j)) {
                            ids.push(parseInt(j));
                        }
                    }
                }
            }
        } else if (fileInfo.fileFeature.hasRootLevelMetaBox) {
            ids = [];
            if (masterContextId === fileInfo.rootLevelMetaBoxProperties.contextId) {
                var imageFeaturesMap = fileInfo.rootLevelMetaBoxProperties.imageFeaturesMap;
                for (i in imageFeaturesMap) {
                    if (imageFeaturesMap.hasOwnProperty(i) && imageFeaturesMap[i].isMasterImage === true) {
                        ids.push(parseInt(i));
                    }
                }
            }
        }
        return ids;
    };

    /** A convenience function to get thumbnail ids.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @return {Array.<number>} Array of thumbnail item IDs. */
    this.getThumbnailIds = function (fileInfo) {
        var ids = null;
        var thumbnailContextId = self.getThumbnailContextId(fileInfo);
        if (fileInfo.trackProperties && fileInfo.trackProperties.length) {
            ids = [];
            for (var i in fileInfo.trackProperties) {
                if (fileInfo.trackProperties[i].trackId === thumbnailContextId) {
                    var sampleProperties = fileInfo.trackProperties[i].sampleProperties;
                    for (var j in sampleProperties) {
                        if (sampleProperties.hasOwnProperty(j)) {
                            ids.push(parseInt(j));
                        }
                    }
                }
            }
        } else if (fileInfo.fileFeature.hasRootLevelMetaBox) {
            ids = [];
            if (thumbnailContextId === fileInfo.rootLevelMetaBoxProperties.contextId) {
                var imageFeaturesMap = fileInfo.rootLevelMetaBoxProperties.imageFeaturesMap;
                for (i in imageFeaturesMap) {
                    if (imageFeaturesMap.hasOwnProperty(i) && imageFeaturesMap[i].isThumbnailImage === true) {
                        ids.push(parseInt(i));
                    }
                }
            }
        }
        return ids;
    };

    /** A convenience function to get meta box thumbnail ids.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @return {Array.<number>} Array of thumbnail item IDs. */
    this.getMetaBoxThumbnailIds = function (fileInfo) {
        var ids = [];
        if (fileInfo.fileFeature.hasRootLevelMetaBox) {
            var imageFeaturesMap = fileInfo.rootLevelMetaBoxProperties.imageFeaturesMap;
            for (var i in imageFeaturesMap) {
                if (imageFeaturesMap.hasOwnProperty(i) && imageFeaturesMap[i].isThumbnailImage === true) {
                    ids.push(parseInt(i));
                }
            }
        }
        return ids;
    };

    /** A convenience function to get meta box master ids.
     *  @param {HEIFReader~FileInfo} fileInfo FileInfo structure.
     *  @return {Array.<number>} Array of thumbnail item IDs. */
    this.getMetaBoxMasterIds = function (fileInfo) {
        var ids = [];
        if (fileInfo.fileFeature.hasRootLevelMetaBox) {
            var imageFeaturesMap = fileInfo.rootLevelMetaBoxProperties.imageFeaturesMap;
            for (var i in imageFeaturesMap) {
                if (imageFeaturesMap.hasOwnProperty(i) && imageFeaturesMap[i].isMasterImage === true) {
                    ids.push(parseInt(i));
                }
            }
        }
        return ids;
    };

    // **** PUBLIC API ENDS ****
}