UNPKG

@loaders.gl/video

Version:

Framework-independent loaders and writers for video (MP4, WEBM, ...)

4 lines 146 kB
{ "version": 3, "sources": ["index.js", "lib/parsers/parse-video.js", "video-loader.js", "lib/utils/assert.js", "lib/gifshot/gifshot.js", "gif-builder.js"], "sourcesContent": ["// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nexport { VideoLoader } from \"./video-loader.js\";\nexport { default as GIFBuilder } from \"./gif-builder.js\";\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n// Parse to platform defined video type (HTMLVideoElement in browser)\nexport default async function parseVideo(arrayBuffer) {\n // TODO It is probably somewhat inefficent to convert a File/Blob to ArrayBuffer and back\n // and could perhaps cause problems for large videos.\n // TODO MIME type is also lost from the File or Response...\n const blob = new Blob([arrayBuffer]);\n const video = document.createElement('video');\n video.src = URL.createObjectURL(blob);\n return video;\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nimport parseVideo from \"./lib/parsers/parse-video.js\";\n// __VERSION__ is injected by babel-plugin-version-inline\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof \"4.3.2\" !== 'undefined' ? \"4.3.2\" : 'latest';\n/**\n * Loads a platform-specific image type that can be used as input data to WebGL textures\n */\nexport const VideoLoader = {\n dataType: null,\n batchType: null,\n name: 'Video',\n id: 'video',\n module: 'video',\n version: VERSION,\n extensions: ['mp4'],\n mimeTypes: ['video/mp4'],\n // tests: arrayBuffer => Boolean(getBinaryImageMetadata(new DataView(arrayBuffer))),\n options: {\n video: {}\n },\n parse: parseVideo\n};\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nexport function assert(condition, message) {\n if (!condition) {\n throw new Error(message);\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n// @ts-nocheck\n/* eslint-disable */\nconst document = globalThis.document || {};\n/* Copyrights for code authored by Yahoo Inc. is licensed under the following terms:\nMIT License\nCopyright 2017 Yahoo Inc.\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\n/*\n utils.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\nvar utils = {\n URL: globalThis.URL || globalThis.webkitURL || globalThis.mozURL || globalThis.msURL,\n getUserMedia: (function () {\n if (!globalThis.navigator)\n return globalThis.navigator;\n const getUserMedia = globalThis.navigator.getUserMedia ||\n globalThis.navigator.webkitGetUserMedia ||\n globalThis.navigator.mozGetUserMedia ||\n globalThis.navigator.msGetUserMedia;\n return getUserMedia ? getUserMedia.bind(globalThis.navigator) : getUserMedia;\n })(),\n requestAnimFrame: globalThis.requestAnimationFrame ||\n globalThis.webkitRequestAnimationFrame ||\n globalThis.mozRequestAnimationFrame ||\n globalThis.oRequestAnimationFrame ||\n globalThis.msRequestAnimationFrame,\n requestTimeout: function requestTimeout(callback, delay) {\n callback = callback || utils.noop;\n delay = delay || 0;\n if (!utils.requestAnimFrame) {\n return setTimeout(callback, delay);\n }\n const start = new Date().getTime();\n const handle = new Object();\n const requestAnimFrame = utils.requestAnimFrame;\n const loop = function loop() {\n const current = new Date().getTime();\n const delta = current - start;\n delta >= delay ? callback.call() : (handle.value = requestAnimFrame(loop));\n };\n handle.value = requestAnimFrame(loop);\n return handle;\n },\n Blob: globalThis.Blob ||\n globalThis.BlobBuilder ||\n globalThis.WebKitBlobBuilder ||\n globalThis.MozBlobBuilder ||\n globalThis.MSBlobBuilder,\n btoa: (function () {\n const btoa = globalThis.btoa ||\n function (input) {\n let output = '';\n let i = 0;\n const l = input.length;\n const key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\n let chr1 = void 0;\n let chr2 = void 0;\n let chr3 = void 0;\n let enc1 = void 0;\n let enc2 = void 0;\n let enc3 = void 0;\n let enc4 = void 0;\n while (i < l) {\n chr1 = input.charCodeAt(i++);\n chr2 = input.charCodeAt(i++);\n chr3 = input.charCodeAt(i++);\n enc1 = chr1 >> 2;\n enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n enc4 = chr3 & 63;\n if (isNaN(chr2)) {\n enc3 = enc4 = 64;\n }\n else if (isNaN(chr3)) {\n enc4 = 64;\n }\n output =\n output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4);\n }\n return output;\n };\n return btoa ? btoa.bind(globalThis) : utils.noop;\n })(),\n isObject: function isObject(obj) {\n return obj && Object.prototype.toString.call(obj) === '[object Object]';\n },\n isEmptyObject: function isEmptyObject(obj) {\n return utils.isObject(obj) && !Object.keys(obj).length;\n },\n isArray: function isArray(arr) {\n return arr && Array.isArray(arr);\n },\n isFunction: function isFunction(func) {\n return func && typeof func === 'function';\n },\n isElement: function isElement(elem) {\n return elem && elem.nodeType === 1;\n },\n isString: function isString(value) {\n return typeof value === 'string' || Object.prototype.toString.call(value) === '[object String]';\n },\n isSupported: {\n canvas: function canvas() {\n const el = document.createElement('canvas');\n return el && el.getContext && el.getContext('2d');\n },\n webworkers: function webworkers() {\n return globalThis.Worker;\n },\n blob: function blob() {\n return utils.Blob;\n },\n Uint8Array: function Uint8Array() {\n return globalThis.Uint8Array;\n },\n Uint32Array: function Uint32Array() {\n return globalThis.Uint32Array;\n },\n videoCodecs: (function () {\n const testEl = document.createElement('video');\n const supportObj = {\n mp4: false,\n h264: false,\n ogv: false,\n ogg: false,\n webm: false\n };\n try {\n if (testEl && testEl.canPlayType) {\n // Check for MPEG-4 support\n supportObj.mp4 = testEl.canPlayType('video/mp4; codecs=\"mp4v.20.8\"') !== '';\n // Check for h264 support\n supportObj.h264 =\n (testEl.canPlayType('video/mp4; codecs=\"avc1.42E01E\"') ||\n testEl.canPlayType('video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"')) !== '';\n // Check for Ogv support\n supportObj.ogv = testEl.canPlayType('video/ogg; codecs=\"theora\"') !== '';\n // Check for Ogg support\n supportObj.ogg = testEl.canPlayType('video/ogg; codecs=\"theora\"') !== '';\n // Check for Webm support\n supportObj.webm = testEl.canPlayType('video/webm; codecs=\"vp8, vorbis\"') !== -1;\n }\n }\n catch (e) { }\n return supportObj;\n })()\n },\n noop: function noop() { },\n each: function each(collection, callback) {\n let x = void 0;\n let len = void 0;\n if (utils.isArray(collection)) {\n x = -1;\n len = collection.length;\n while (++x < len) {\n if (callback(x, collection[x]) === false) {\n break;\n }\n }\n }\n else if (utils.isObject(collection)) {\n for (x in collection) {\n if (collection.hasOwnProperty(x)) {\n if (callback(x, collection[x]) === false) {\n break;\n }\n }\n }\n }\n },\n normalizeOptions: function normalizeOptions(defaultOptions, userOptions) {\n if (!utils.isObject(defaultOptions) || !utils.isObject(userOptions) || !Object.keys) {\n return;\n }\n const newObj = {};\n utils.each(defaultOptions, function (key, val) {\n newObj[key] = defaultOptions[key];\n });\n utils.each(userOptions, function (key, val) {\n const currentUserOption = userOptions[key];\n if (!utils.isObject(currentUserOption)) {\n newObj[key] = currentUserOption;\n }\n else if (!defaultOptions[key]) {\n newObj[key] = currentUserOption;\n }\n else {\n newObj[key] = utils.normalizeOptions(defaultOptions[key], currentUserOption);\n }\n });\n return newObj;\n },\n setCSSAttr: function setCSSAttr(elem, attr, val) {\n if (!utils.isElement(elem)) {\n return;\n }\n if (utils.isString(attr) && utils.isString(val)) {\n elem.style[attr] = val;\n }\n else if (utils.isObject(attr)) {\n utils.each(attr, function (key, val) {\n elem.style[key] = val;\n });\n }\n },\n removeElement: function removeElement(node) {\n if (!utils.isElement(node)) {\n return;\n }\n if (node.parentNode) {\n node.parentNode.removeChild(node);\n }\n },\n createWebWorker: function createWebWorker(content) {\n if (!utils.isString(content)) {\n return {};\n }\n try {\n const blob = new utils.Blob([content], {\n type: 'text/javascript'\n });\n const objectUrl = utils.URL.createObjectURL(blob);\n const worker = new Worker(objectUrl);\n return {\n objectUrl,\n worker\n };\n }\n catch (e) {\n return `${e}`;\n }\n },\n getExtension: function getExtension(src) {\n return src.substr(src.lastIndexOf('.') + 1, src.length);\n },\n getFontSize: function getFontSize() {\n const options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n if (!document.body || options.resizeFont === false) {\n return options.fontSize;\n }\n const text = options.text;\n const containerWidth = options.gifWidth;\n let fontSize = parseInt(options.fontSize, 10);\n const minFontSize = parseInt(options.minFontSize, 10);\n const div = document.createElement('div');\n const span = document.createElement('span');\n div.setAttribute('width', containerWidth);\n div.appendChild(span);\n span.innerHTML = text;\n span.style.fontSize = `${fontSize}px`;\n span.style.textIndent = '-9999px';\n span.style.visibility = 'hidden';\n document.body.appendChild(span);\n while (span.offsetWidth > containerWidth && fontSize >= minFontSize) {\n span.style.fontSize = `${--fontSize}px`;\n }\n document.body.removeChild(span);\n return `${fontSize}px`;\n },\n webWorkerError: false\n};\nconst utils$2 = Object.freeze({\n default: utils\n});\n/*\n error.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\n// Dependencies\nvar error = {\n validate: function validate(skipObj) {\n skipObj = utils.isObject(skipObj) ? skipObj : {};\n let errorObj = {};\n utils.each(error.validators, function (indece, currentValidator) {\n const errorCode = currentValidator.errorCode;\n if (!skipObj[errorCode] && !currentValidator.condition) {\n errorObj = currentValidator;\n errorObj.error = true;\n return false;\n }\n });\n delete errorObj.condition;\n return errorObj;\n },\n isValid: function isValid(skipObj) {\n const errorObj = error.validate(skipObj);\n const isValid = errorObj.error !== true;\n return isValid;\n },\n validators: [\n {\n condition: utils.isFunction(utils.getUserMedia),\n errorCode: 'getUserMedia',\n errorMsg: 'The getUserMedia API is not supported in your browser'\n },\n {\n condition: utils.isSupported.canvas(),\n errorCode: 'canvas',\n errorMsg: 'Canvas elements are not supported in your browser'\n },\n {\n condition: utils.isSupported.webworkers(),\n errorCode: 'webworkers',\n errorMsg: 'The Web Workers API is not supported in your browser'\n },\n {\n condition: utils.isFunction(utils.URL),\n errorCode: 'globalThis.URL',\n errorMsg: 'The globalThis.URL API is not supported in your browser'\n },\n {\n condition: utils.isSupported.blob(),\n errorCode: 'globalThis.Blob',\n errorMsg: 'The globalThis.Blob File API is not supported in your browser'\n },\n {\n condition: utils.isSupported.Uint8Array(),\n errorCode: 'globalThis.Uint8Array',\n errorMsg: 'The globalThis.Uint8Array function constructor is not supported in your browser'\n },\n {\n condition: utils.isSupported.Uint32Array(),\n errorCode: 'globalThis.Uint32Array',\n errorMsg: 'The globalThis.Uint32Array function constructor is not supported in your browser'\n }\n ],\n messages: {\n videoCodecs: {\n errorCode: 'videocodec',\n errorMsg: 'The video codec you are trying to use is not supported in your browser'\n }\n }\n};\nconst error$2 = Object.freeze({\n default: error\n});\n/*\n defaultOptions.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\n// Helpers\nconst noop = function noop() { };\nconst defaultOptions = {\n sampleInterval: 10,\n numWorkers: 2,\n filter: '',\n gifWidth: 200,\n gifHeight: 200,\n interval: 0.1,\n numFrames: 10,\n frameDuration: 1,\n keepCameraOn: false,\n images: [],\n video: null,\n webcamVideoElement: null,\n cameraStream: null,\n text: '',\n fontWeight: 'normal',\n fontSize: '16px',\n minFontSize: '10px',\n resizeFont: false,\n fontFamily: 'sans-serif',\n fontColor: '#ffffff',\n textAlign: 'center',\n textBaseline: 'bottom',\n textXCoordinate: null,\n textYCoordinate: null,\n progressCallback: noop,\n completeCallback: noop,\n saveRenderingContexts: false,\n savedRenderingContexts: [],\n crossOrigin: 'Anonymous'\n};\nconst defaultOptions$2 = Object.freeze({\n default: defaultOptions\n});\n/*\n isSupported.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\n// Dependencies\nfunction isSupported() {\n return error.isValid();\n}\n/*\n isWebCamGIFSupported.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\nfunction isWebCamGIFSupported() {\n return error.isValid();\n}\n/*\n isSupported.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\n// Dependencies\nfunction isSupported$1() {\n const options = {\n getUserMedia: true\n };\n return error.isValid(options);\n}\n/*\n isExistingVideoGIFSupported.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\n// Dependencies\nfunction isExistingVideoGIFSupported(codecs) {\n let hasValidCodec = false;\n if (utils.isArray(codecs) && codecs.length) {\n utils.each(codecs, function (indece, currentCodec) {\n if (utils.isSupported.videoCodecs[currentCodec]) {\n hasValidCodec = true;\n }\n });\n if (!hasValidCodec) {\n return false;\n }\n }\n else if (utils.isString(codecs) && codecs.length) {\n if (!utils.isSupported.videoCodecs[codecs]) {\n return false;\n }\n }\n return error.isValid({\n getUserMedia: true\n });\n}\n/*\n NeuQuant.js\n */\n/*\n * NeuQuant Neural-Net Quantization Algorithm\n * ------------------------------------------\n *\n * Copyright (c) 1994 Anthony Dekker\n *\n * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See\n * \"Kohonen neural networks for optimal colour quantization\" in \"Network:\n * Computation in Neural Systems\" Vol. 5 (1994) pp 351-367. for a discussion of\n * the algorithm.\n *\n * Any party obtaining a copy of these files from the author, directly or\n * indirectly, is granted, free of charge, a full and unrestricted irrevocable,\n * world-wide, paid up, royalty-free, nonexclusive right and license to deal in\n * this software and documentation files (the \"Software\"), including without\n * limitation the rights to use, copy, modify, merge, publish, distribute,\n * sublicense, and/or sell copies of the Software, and to permit persons who\n * receive copies from any such party to do so, with the only requirement being\n * that this copyright notice remain intact.\n */\n/*\n * This class handles Neural-Net quantization algorithm\n * @author Kevin Weiner (original Java version - kweiner@fmsware.com)\n * @author Thibault Imbert (AS3 version - bytearray.org)\n * @version 0.1 AS3 implementation\n * @version 0.2 JS->AS3 \"translation\" by antimatter15\n * @version 0.3 JS clean up + using modern JS idioms by sole - http://soledadpenades.com\n * Also implement fix in color conversion described at http://stackoverflow.com/questions/16371712/neuquant-js-javascript-color-quantization-hidden-bug-in-js-conversion\n */\nfunction NeuQuant() {\n const netsize = 256; // number of colours used\n // four primes near 500 - assume no image has a length so large\n // that it is divisible by all four primes\n const prime1 = 499;\n const prime2 = 491;\n const prime3 = 487;\n const prime4 = 503;\n // minimum size for input image\n const minpicturebytes = 3 * prime4;\n // Network Definitions\n const maxnetpos = netsize - 1;\n const netbiasshift = 4; // bias for colour values\n const ncycles = 100; // no. of learning cycles\n // defs for freq and bias\n const intbiasshift = 16; // bias for fractions\n const intbias = 1 << intbiasshift;\n const gammashift = 10; // gamma = 1024\n const gamma = 1 << gammashift;\n const betashift = 10;\n const beta = intbias >> betashift; // beta = 1/1024\n const betagamma = intbias << (gammashift - betashift);\n // defs for decreasing radius factor\n // For 256 colors, radius starts at 32.0 biased by 6 bits\n // and decreases by a factor of 1/30 each cycle\n const initrad = netsize >> 3;\n const radiusbiasshift = 6;\n const radiusbias = 1 << radiusbiasshift;\n const initradius = initrad * radiusbias;\n const radiusdec = 30;\n // defs for decreasing alpha factor\n // Alpha starts at 1.0 biased by 10 bits\n const alphabiasshift = 10;\n const initalpha = 1 << alphabiasshift;\n let alphadec;\n // radbias and alpharadbias used for radpower calculation\n const radbiasshift = 8;\n const radbias = 1 << radbiasshift;\n const alpharadbshift = alphabiasshift + radbiasshift;\n const alpharadbias = 1 << alpharadbshift;\n // Input image\n let thepicture;\n // Height * Width * 3\n let lengthcount;\n // Sampling factor 1..30\n let samplefac;\n // The network itself\n let network;\n const netindex = [];\n // for network lookup - really 256\n const bias = [];\n // bias and freq arrays for learning\n const freq = [];\n const radpower = [];\n function NeuQuantConstructor(thepic, len, sample) {\n let i;\n let p;\n thepicture = thepic;\n lengthcount = len;\n samplefac = sample;\n network = new Array(netsize);\n for (i = 0; i < netsize; i++) {\n network[i] = new Array(4);\n p = network[i];\n p[0] = p[1] = p[2] = ((i << (netbiasshift + 8)) / netsize) | 0;\n freq[i] = (intbias / netsize) | 0; // 1 / netsize\n bias[i] = 0;\n }\n }\n function colorMap() {\n const map = [];\n const index = new Array(netsize);\n for (let i = 0; i < netsize; i++) {\n index[network[i][3]] = i;\n }\n let k = 0;\n for (let l = 0; l < netsize; l++) {\n const j = index[l];\n map[k++] = network[j][0];\n map[k++] = network[j][1];\n map[k++] = network[j][2];\n }\n return map;\n }\n // Insertion sort of network and building of netindex[0..255]\n // (to do after unbias)\n function inxbuild() {\n let i;\n let j;\n let smallpos;\n let smallval;\n let p;\n let q;\n let previouscol;\n let startpos;\n previouscol = 0;\n startpos = 0;\n for (i = 0; i < netsize; i++) {\n p = network[i];\n smallpos = i;\n smallval = p[1]; // index on g\n // find smallest in i..netsize-1\n for (j = i + 1; j < netsize; j++) {\n q = network[j];\n if (q[1] < smallval) {\n // index on g\n smallpos = j;\n smallval = q[1]; // index on g\n }\n }\n q = network[smallpos];\n // swap p (i) and q (smallpos) entries\n if (i != smallpos) {\n j = q[0];\n q[0] = p[0];\n p[0] = j;\n j = q[1];\n q[1] = p[1];\n p[1] = j;\n j = q[2];\n q[2] = p[2];\n p[2] = j;\n j = q[3];\n q[3] = p[3];\n p[3] = j;\n }\n // smallval entry is now in position i\n if (smallval != previouscol) {\n netindex[previouscol] = (startpos + i) >> 1;\n for (j = previouscol + 1; j < smallval; j++) {\n netindex[j] = i;\n }\n previouscol = smallval;\n startpos = i;\n }\n }\n netindex[previouscol] = (startpos + maxnetpos) >> 1;\n for (j = previouscol + 1; j < 256; j++) {\n netindex[j] = maxnetpos; // really 256\n }\n }\n // Main Learning Loop\n function learn() {\n let i;\n let j;\n let b;\n let g;\n let r;\n let radius;\n let rad;\n let alpha;\n let step;\n let delta;\n let samplepixels;\n let p;\n let pix;\n let lim;\n if (lengthcount < minpicturebytes) {\n samplefac = 1;\n }\n alphadec = 30 + (samplefac - 1) / 3;\n p = thepicture;\n pix = 0;\n lim = lengthcount;\n samplepixels = lengthcount / (3 * samplefac);\n delta = (samplepixels / ncycles) | 0;\n alpha = initalpha;\n radius = initradius;\n rad = radius >> radiusbiasshift;\n if (rad <= 1) {\n rad = 0;\n }\n for (i = 0; i < rad; i++) {\n radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));\n }\n if (lengthcount < minpicturebytes) {\n step = 3;\n }\n else if (lengthcount % prime1 !== 0) {\n step = 3 * prime1;\n }\n else if (lengthcount % prime2 !== 0) {\n step = 3 * prime2;\n }\n else if (lengthcount % prime3 !== 0) {\n step = 3 * prime3;\n }\n else {\n step = 3 * prime4;\n }\n i = 0;\n while (i < samplepixels) {\n b = (p[pix + 0] & 0xff) << netbiasshift;\n g = (p[pix + 1] & 0xff) << netbiasshift;\n r = (p[pix + 2] & 0xff) << netbiasshift;\n j = contest(b, g, r);\n altersingle(alpha, j, b, g, r);\n if (rad !== 0) {\n // Alter neighbours\n alterneigh(rad, j, b, g, r);\n }\n pix += step;\n if (pix >= lim) {\n pix -= lengthcount;\n }\n i++;\n if (delta === 0) {\n delta = 1;\n }\n if (i % delta === 0) {\n alpha -= alpha / alphadec;\n radius -= radius / radiusdec;\n rad = radius >> radiusbiasshift;\n if (rad <= 1) {\n rad = 0;\n }\n for (j = 0; j < rad; j++) {\n radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));\n }\n }\n }\n }\n // Search for BGR values 0..255 (after net is unbiased) and return colour index\n function map(b, g, r) {\n let i;\n let j;\n let dist;\n let a;\n let bestd;\n let p;\n let best;\n // Biggest possible distance is 256 * 3\n bestd = 1000;\n best = -1;\n i = netindex[g]; // index on g\n j = i - 1; // start at netindex[g] and work outwards\n while (i < netsize || j >= 0) {\n if (i < netsize) {\n p = network[i];\n dist = p[1] - g; // inx key\n if (dist >= bestd) {\n i = netsize; // stop iter\n }\n else {\n i++;\n if (dist < 0) {\n dist = -dist;\n }\n a = p[0] - b;\n if (a < 0) {\n a = -a;\n }\n dist += a;\n if (dist < bestd) {\n a = p[2] - r;\n if (a < 0) {\n a = -a;\n }\n dist += a;\n if (dist < bestd) {\n bestd = dist;\n best = p[3];\n }\n }\n }\n }\n if (j >= 0) {\n p = network[j];\n dist = g - p[1]; // inx key - reverse dif\n if (dist >= bestd) {\n j = -1; // stop iter\n }\n else {\n j--;\n if (dist < 0) {\n dist = -dist;\n }\n a = p[0] - b;\n if (a < 0) {\n a = -a;\n }\n dist += a;\n if (dist < bestd) {\n a = p[2] - r;\n if (a < 0) {\n a = -a;\n }\n dist += a;\n if (dist < bestd) {\n bestd = dist;\n best = p[3];\n }\n }\n }\n }\n }\n return best;\n }\n function process() {\n learn();\n unbiasnet();\n inxbuild();\n return colorMap();\n }\n // Unbias network to give byte values 0..255 and record position i\n // to prepare for sort\n function unbiasnet() {\n let i;\n let j;\n for (i = 0; i < netsize; i++) {\n network[i][0] >>= netbiasshift;\n network[i][1] >>= netbiasshift;\n network[i][2] >>= netbiasshift;\n network[i][3] = i; // record colour no\n }\n }\n // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2))\n // in radpower[|i-j|]\n function alterneigh(rad, i, b, g, r) {\n let j;\n let k;\n let lo;\n let hi;\n let a;\n let m;\n let p;\n lo = i - rad;\n if (lo < -1) {\n lo = -1;\n }\n hi = i + rad;\n if (hi > netsize) {\n hi = netsize;\n }\n j = i + 1;\n k = i - 1;\n m = 1;\n while (j < hi || k > lo) {\n a = radpower[m++];\n if (j < hi) {\n p = network[j++];\n try {\n p[0] -= ((a * (p[0] - b)) / alpharadbias) | 0;\n p[1] -= ((a * (p[1] - g)) / alpharadbias) | 0;\n p[2] -= ((a * (p[2] - r)) / alpharadbias) | 0;\n }\n catch (e) { }\n }\n if (k > lo) {\n p = network[k--];\n try {\n p[0] -= ((a * (p[0] - b)) / alpharadbias) | 0;\n p[1] -= ((a * (p[1] - g)) / alpharadbias) | 0;\n p[2] -= ((a * (p[2] - r)) / alpharadbias) | 0;\n }\n catch (e) { }\n }\n }\n }\n // Move neuron i towards biased (b,g,r) by factor alpha\n function altersingle(alpha, i, b, g, r) {\n // alter hit neuron\n const n = network[i];\n const alphaMult = alpha / initalpha;\n n[0] -= (alphaMult * (n[0] - b)) | 0;\n n[1] -= (alphaMult * (n[1] - g)) | 0;\n n[2] -= (alphaMult * (n[2] - r)) | 0;\n }\n // Search for biased BGR values\n function contest(b, g, r) {\n // finds closest neuron (min dist) and updates freq\n // finds best neuron (min dist-bias) and returns position\n // for frequently chosen neurons, freq[i] is high and bias[i] is negative\n // bias[i] = gamma*((1/netsize)-freq[i])\n let i;\n let dist;\n let a;\n let biasdist;\n let betafreq;\n let bestpos;\n let bestbiaspos;\n let bestd;\n let bestbiasd;\n let n;\n bestd = ~(1 << 31);\n bestbiasd = bestd;\n bestpos = -1;\n bestbiaspos = bestpos;\n for (i = 0; i < netsize; i++) {\n n = network[i];\n dist = n[0] - b;\n if (dist < 0) {\n dist = -dist;\n }\n a = n[1] - g;\n if (a < 0) {\n a = -a;\n }\n dist += a;\n a = n[2] - r;\n if (a < 0) {\n a = -a;\n }\n dist += a;\n if (dist < bestd) {\n bestd = dist;\n bestpos = i;\n }\n biasdist = dist - (bias[i] >> (intbiasshift - netbiasshift));\n if (biasdist < bestbiasd) {\n bestbiasd = biasdist;\n bestbiaspos = i;\n }\n betafreq = freq[i] >> betashift;\n freq[i] -= betafreq;\n bias[i] += betafreq << gammashift;\n }\n freq[bestpos] += beta;\n bias[bestpos] -= betagamma;\n return bestbiaspos;\n }\n NeuQuantConstructor.apply(this, arguments);\n const exports = {};\n exports.map = map;\n exports.process = process;\n return exports;\n}\n/*\n processFrameWorker.js\n */\n/* Copyright 2017 Yahoo Inc.\n * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.\n */\nfunction workerCode() {\n const self = this;\n try {\n globalThis.onmessage = function (ev) {\n const data = ev.data || {};\n let response;\n if (data.gifshot) {\n response = workerMethods.run(data);\n postMessage(response);\n }\n };\n }\n catch (e) { }\n var workerMethods = {\n dataToRGB: function dataToRGB(data, width, height) {\n const length = width * height * 4;\n let i = 0;\n const rgb = [];\n while (i < length) {\n rgb.push(data[i++]);\n rgb.push(data[i++]);\n rgb.push(data[i++]);\n i++; // for the alpha channel which we don't care about\n }\n return rgb;\n },\n componentizedPaletteToArray: function componentizedPaletteToArray(paletteRGB) {\n paletteRGB = paletteRGB || [];\n const paletteArray = [];\n for (let i = 0; i < paletteRGB.length; i += 3) {\n const r = paletteRGB[i];\n const g = paletteRGB[i + 1];\n const b = paletteRGB[i + 2];\n paletteArray.push((r << 16) | (g << 8) | b);\n }\n return paletteArray;\n },\n // This is the \"traditional\" Animated_GIF style of going from RGBA to indexed color frames\n processFrameWithQuantizer: function processFrameWithQuantizer(imageData, width, height, sampleInterval) {\n const rgbComponents = this.dataToRGB(imageData, width, height);\n const nq = new NeuQuant(rgbComponents, rgbComponents.length, sampleInterval);\n const paletteRGB = nq.process();\n const paletteArray = new Uint32Array(this.componentizedPaletteToArray(paletteRGB));\n const numberPixels = width * height;\n const indexedPixels = new Uint8Array(numberPixels);\n let k = 0;\n for (let i = 0; i < numberPixels; i++) {\n const r = rgbComponents[k++];\n const g = rgbComponents[k++];\n const b = rgbComponents[k++];\n indexedPixels[i] = nq.map(r, g, b);\n }\n return {\n pixels: indexedPixels,\n palette: paletteArray\n };\n },\n run: function run(frame) {\n frame = frame || {};\n const _frame = frame;\n const height = _frame.height;\n const palette = _frame.palette;\n const sampleInterval = _frame.sampleInterval;\n const width = _frame.width;\n const imageData = frame.data;\n return this.processFrameWithQuantizer(imageData, width, height, sampleInterval);\n }\n };\n return workerMethods;\n}\n/*\n gifWriter.js\n */\n// (c) Dean McNamee <dean@gmail.com>, 2013.\n//\n// https://github.com/deanm/omggif\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to\n// deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n// sell copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in\n// all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n// IN THE SOFTWARE.\n//\n// omggif is a JavaScript implementation of a GIF 89a encoder and decoder,\n// including animation and compression. It does not rely on any specific\n// underlying system, so should run in the browser, Node, or Plask.\nfunction gifWriter(buf, width, height, gopts) {\n let p = 0;\n gopts = gopts === undefined ? {} : gopts;\n const loop_count = gopts.loop === undefined ? null : gopts.loop;\n const global_palette = gopts.palette === undefined ? null : gopts.palette;\n if (width <= 0 || height <= 0 || width > 65535 || height > 65535)\n throw 'Width/Height invalid.';\n function check_palette_and_num_colors(palette) {\n const num_colors = palette.length;\n if (num_colors < 2 || num_colors > 256 || num_colors & (num_colors - 1))\n throw 'Invalid code/color length, must be power of 2 and 2 .. 256.';\n return num_colors;\n }\n // - Header.\n buf[p++] = 0x47;\n buf[p++] = 0x49;\n buf[p++] = 0x46; // GIF\n buf[p++] = 0x38;\n buf[p++] = 0x39;\n buf[p++] = 0x61; // 89a\n // Handling of Global Color Table (palette) and background index.\n const gp_num_colors_pow2 = 0;\n const background = 0;\n // - Logical Screen Descriptor.\n // NOTE(deanm): w/h apparently ignored by implementations, but set anyway.\n buf[p++] = width & 0xff;\n buf[p++] = (width >> 8) & 0xff;\n buf[p++] = height & 0xff;\n buf[p++] = (height >> 8) & 0xff;\n // NOTE: Indicates 0-bpp original color resolution (unused?).\n buf[p++] =\n (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag.\n gp_num_colors_pow2; // NOTE: No sort flag (unused?).\n buf[p++] = background; // Background Color Index.\n buf[p++] = 0; // Pixel aspect ratio (unused?).\n if (loop_count !== null) {\n // Netscape block for looping.\n if (loop_count < 0 || loop_count > 65535)\n throw 'Loop count invalid.';\n // Extension code, label, and length.\n buf[p++] = 0x21;\n buf[p++] = 0xff;\n buf[p++] = 0x0b;\n // NETSCAPE2.0\n buf[p++] = 0x4e;\n buf[p++] = 0x45;\n buf[p++] = 0x54;\n buf[p++] = 0x53;\n buf[p++] = 0x43;\n buf[p++] = 0x41;\n buf[p++] = 0x50;\n buf[p++] = 0x45;\n buf[p++] = 0x32;\n buf[p++] = 0x2e;\n buf[p++] = 0x30;\n // Sub-block\n buf[p++] = 0x03;\n buf[p++] = 0x01;\n buf[p++] = loop_count & 0xff;\n buf[p++] = (loop_count >> 8) & 0xff;\n buf[p++] = 0x00; // Terminator.\n }\n let ended = false;\n this.addFrame = function (x, y, w, h, indexed_pixels, opts) {\n if (ended === true) {\n --p;\n ended = false;\n } // Un-end.\n opts = opts === undefined ? {} : opts;\n // TODO(deanm): Bounds check x, y. Do they need to be within the virtual\n // canvas width/height, I imagine?\n if (x < 0 || y < 0 || x > 65535 || y > 65535)\n throw 'x/y invalid.';\n if (w <= 0 || h <= 0 || w > 65535 || h > 65535)\n throw 'Width/Height invalid.';\n if (indexed_pixels.length < w * h)\n throw 'Not enough pixels for the frame size.';\n let using_local_palette = true;\n let palette = opts.palette;\n if (palette === undefined || palette === null) {\n using_local_palette = false;\n palette = global_palette;\n }\n if (palette === undefined || palette === null)\n throw 'Must supply either a local or global palette.';\n let num_colors = check_palette_and_num_colors(palette);\n // Compute the min_code_size (power of 2), destroying num_colors.\n let min_code_size = 0;\n while ((num_colors >>= 1)) {\n ++min_code_size;\n }\n num_colors = 1 << min_code_size; // Now we can easily get it back.\n const delay = opts.delay === undefined ? 0 : opts.delay;\n // From the spec:\n // 0 - No disposal specified. The decoder is\n // not required to take any action.\n // 1 - Do not dispose. The graphic is to be left\n // in place.\n // 2 - Restore to background color. The area used by the\n // graphic must be restored to the background color.\n // 3 - Restore to previous. The decoder is required to\n // restore the area overwritten by the graphic with\n // what was there prior to rendering the graphic.\n // 4-7 - To be defined.\n // NOTE(deanm): Dispose background doesn't really work, apparently most\n // browsers ignore the background palette index and clear to transparency.\n const disposal = opts.disposal === undefined ? 0 : opts.disposal;\n if (disposal < 0 || disposal > 3)\n // 4-7 is reserved.\n throw 'Disposal out of range.';\n let use_transparency = false;\n let transparent_index = 0;\n if (opts.transparent !== undefined && opts.transparent !== null) {\n use_transparency = true;\n transparent_index = opts.transparent;\n if (transparent_index < 0 || transparent_index >= num_colors)\n throw 'Transparent color index.';\n }\n if (disposal !== 0 || use_transparency || delay !== 0) {\n // - Graphics Control Extension\n buf[p++] = 0x21;\n buf[p++] = 0xf9; // Extension / Label.\n buf[p++] = 4; // Byte size.\n buf[p++] = (disposal << 2) | (use_transparency === true ? 1 : 0);\n buf[p++] = delay & 0xff;\n buf[p++] = (delay >> 8) & 0xff;\n buf[p++] = transparent_index; // Transparent color index.\n buf[p++] = 0; // Block Terminator.\n }\n // - Image Descriptor\n buf[p++] = 0x2c; // Image Seperator.\n buf[p++] = x & 0xff;\n buf[p++] = (x >> 8) & 0xff; // Left.\n buf[p++] = y & 0xff;\n buf[p++] = (y >> 8) & 0xff; // Top.\n buf[p++] = w & 0xff;\n buf[p++] = (w >> 8) & 0xff;\n buf[p++] = h & 0xff;\n buf[p++] = (h >> 8) & 0xff;\n // NOTE: No sort flag (unused?).\n // TODO(deanm): Support interlace.\n buf[p++] = using_local_palette === true ? 0x80 | (min_code_size - 1) : 0;\n // - Local Color Table\n if (using_local_palette === true) {\n for (let i = 0, il = palette.length; i < il; ++i) {\n const rgb = palette[i];\n buf[p++] = (rgb >> 16) & 0xff;\n buf[p++] = (rgb >> 8) & 0xff;\n buf[p++] = rgb & 0xff;\n }\n }\n p = GifWriterOutputLZWCodeStream(buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels);\n };\n this.end = function () {\n if (ended === false) {\n buf[p++] = 0x3b; // Trailer.\n ended = true;\n }\n return p;\n };\n // Main compression routine, palette indexes -> LZW code stream.\n // |index_stream| must have at least one entry.\n function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) {\n buf[p++] = min_code_size;\n let cur_subblock = p++; // Pointing at the length field.\n const clear_code = 1 << min_code_size;\n const code_mask = clear_code - 1;\n const eoi_code = clear_code + 1;\n let next_code = eoi_code + 1;\n let cur_code_size = min_code_size + 1; // Number of bits per code.\n let cur_shift = 0;\n // We have at most 12-bit codes, so we should have to hold a max of 19\n // bits here (and then we would write out).\n let cur = 0;\n function emit_bytes_to_buffer(bit_block_size) {\n while (cur_shift >= bit_block_size) {\n buf[p++] = cur & 0xff;\n cur >>= 8;\n cur_shift -= 8;\n if (p === cur_subblock + 256) {\n // Finished a subblock.\n buf[cur_subblock] = 255;\n cur_subblock = p++;\n }\n }\n }\n function emit_code(c) {\n cur |= c << cur_shift;\n cur_shift += cur_code_size;\n emit_bytes_to_buffer(8);\n }\n // I am not an expert on the topic, and I don't want to write a thesis.\n // However, it is good to outline here the basic algorithm and the few data\n // structures and optimizations here that make this implementation fast.\n // The basic idea behind LZW is to build a table of previously seen runs\n // addressed by a short id (herein called output code). All data is\n // referenced by a code, which represents one or more values from the\n // original input stream. All input bytes can be referenced as the same\n // value as an output code. So if you didn't want any compression, you\n // could more or less just output the original bytes as codes (there are\n // some details to this, but it is the idea). In order to achieve\n // compression, values greater then the input range (codes can be up to\n // 12-bit while input only 8-bit) represent a sequence of previously seen\n // inputs. The decompressor is able to build the same mapping while\n // decoding, so there is always a shared common knowledge between the\n // encoding and decoder, which is also important for \"timing\" aspects like\n // how to handle variable bit width code encoding.\n //\n // One obvious but very important consequence of the table system is there\n // is always a unique id (at most 12-bits) to map the runs. 'A' might be\n // 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship\n // can be used for an effecient lookup strategy for the code mapping. We\n // need to know if a run has been seen before, and be able to map that run\n // to the output code. Since we start with known unique ids (input bytes),\n // and then from those build more unique ids (table entries), we can\n // continue this chain (almost like a linked list) to always have small\n // integer values that represent the current byte chains in the encoder.\n // This means instead of tracking the input bytes (AAAABCD) to know our\n // current state, we can track the table entry for AAAABC (it is guaranteed\n // to exist by the nature of the algorithm) and the next character D.\n // Therefor the tuple of (table_entry, byte) is guaranteed to also be\n // unique. This allows us to create a simple lookup key for mapping input\n // sequences to codes (table indices) without having to store or search\n // any of the code sequences. So if 'AAAA' has a table entry of 12, the\n // tuple of ('AAAA', K) for any input byte K will be unique, and can be our\n // key. This leads to a integer value at most 20-bits, which can always\n // fit in an SMI value and be used as a fast sparse array / object key.\n // Output code for the current contents of the index buffer.\n let ib_code = index_stream[0] & code_mask; // Load first input index.\n let code_table = {}; // Key'd on our 20-bit \"tuple\".\n emit_code(clear_code); // Spec says first code should be a clear code.\n // First index already loaded, process the rest of the stream.\n for (let i = 1, il = index_stream.length; i < il; ++i) {\n const k = index_stream[i] & code_mask;\n const cur_key = (ib_code << 8) | k; // (prev, k) unique tuple.\n const cur_code = code_table[cur_key]; // buffer + k.\n // Check if we have to create a new code table entry.\n if (cur_code === undefined) {\n // We don't have buffer + k.\n // Emit index buffer (without k).\n // This is an inline version of emit_code, because this is the core\n // writing rout