Spaces:
Running
Running
Download fast-average-color.js from node-user1/ncp: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/spaces/node-user1/ncp/resolve/main/fast-average-color.js
- Command line
-
hf download hf://spaces/node-user1/ncp/fast-average-color.js
-
curl -L -o fast-average-color.js https://huggingface.co/spaces/node-user1/ncp/resolve/main/fast-average-color.js
15.2 kB
| /*! Fast Average Color | © 2022 Denis Seleznev | MIT License | https://github.com/fast-average-color/fast-average-color */ | |
| function toHex(num) { | |
| let str = num.toString(16) | |
| return str.length === 1 ? '0' + str : str | |
| } | |
| function arrayToHex(arr) { | |
| return '#' + arr.map(toHex).join('') | |
| } | |
| function isDark(color) { | |
| // http://www.w3.org/TR/AERT#color-contrast | |
| let result = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000 | |
| return result < 128 | |
| } | |
| function prepareIgnoredColor(color) { | |
| if (!color) { | |
| return [] | |
| } | |
| return isRGBArray(color) ? color : [color] | |
| } | |
| function isRGBArray(value) { | |
| return Array.isArray(value[0]) | |
| } | |
| function isIgnoredColor(data, index, ignoredColor) { | |
| for (let i = 0; i < ignoredColor.length; i++) { | |
| if (isIgnoredColorAsNumbers(data, index, ignoredColor[i])) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| function isIgnoredColorAsNumbers(data, index, ignoredColor) { | |
| switch (ignoredColor.length) { | |
| case 3: | |
| // [red, green, blue] | |
| if (isIgnoredRGBColor(data, index, ignoredColor)) { | |
| return true | |
| } | |
| break | |
| case 4: | |
| // [red, green, blue, alpha] | |
| if (isIgnoredRGBAColor(data, index, ignoredColor)) { | |
| return true | |
| } | |
| break | |
| case 5: | |
| // [red, green, blue, alpha, threshold] | |
| if (isIgnoredRGBAColorWithThreshold(data, index, ignoredColor)) { | |
| return true | |
| } | |
| break | |
| default: | |
| return false | |
| } | |
| } | |
| function isIgnoredRGBColor(data, index, ignoredColor) { | |
| // Ignore if the pixel are transparent. | |
| if (data[index + 3] !== 255) { | |
| return true | |
| } | |
| if (data[index] === ignoredColor[0] && | |
| data[index + 1] === ignoredColor[1] && | |
| data[index + 2] === ignoredColor[2]) { | |
| return true | |
| } | |
| return false | |
| } | |
| function isIgnoredRGBAColor(data, index, ignoredColor) { | |
| if (data[index + 3] && ignoredColor[3]) { | |
| return data[index] === ignoredColor[0] && | |
| data[index + 1] === ignoredColor[1] && | |
| data[index + 2] === ignoredColor[2] && | |
| data[index + 3] === ignoredColor[3] | |
| } | |
| // Ignore rgb components if the pixel are fully transparent. | |
| return data[index + 3] === ignoredColor[3] | |
| } | |
| function inRange(colorComponent, ignoredColorComponent, value) { | |
| return colorComponent >= (ignoredColorComponent - value) && | |
| colorComponent <= (ignoredColorComponent + value) | |
| } | |
| function isIgnoredRGBAColorWithThreshold(data, index, ignoredColor) { | |
| let redIgnored = ignoredColor[0] | |
| let greenIgnored = ignoredColor[1] | |
| let blueIgnored = ignoredColor[2] | |
| let alphaIgnored = ignoredColor[3] | |
| let threshold = ignoredColor[4] | |
| let alphaData = data[index + 3] | |
| let alphaInRange = inRange(alphaData, alphaIgnored, threshold) | |
| if (!alphaIgnored) { | |
| return alphaInRange | |
| } | |
| if (!alphaData && alphaInRange) { | |
| return true | |
| } | |
| if (inRange(data[index], redIgnored, threshold) && | |
| inRange(data[index + 1], greenIgnored, threshold) && | |
| inRange(data[index + 2], blueIgnored, threshold) && | |
| alphaInRange) { | |
| return true | |
| } | |
| return false | |
| } | |
| function dominantAlgorithm(arr, len, options) { | |
| let colorHash = {} | |
| let divider = 24 | |
| let ignoredColor = options.ignoredColor | |
| let step = options.step | |
| let max = [0, 0, 0, 0, 0] | |
| for (let i = 0; i < len; i += step) { | |
| let red = arr[i] | |
| let green = arr[i + 1] | |
| let blue = arr[i + 2] | |
| let alpha = arr[i + 3] | |
| if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) { | |
| continue | |
| } | |
| let key = Math.round(red / divider) + ',' + | |
| Math.round(green / divider) + ',' + | |
| Math.round(blue / divider) | |
| if (colorHash[key]) { | |
| colorHash[key] = [ | |
| colorHash[key][0] + red * alpha, | |
| colorHash[key][1] + green * alpha, | |
| colorHash[key][2] + blue * alpha, | |
| colorHash[key][3] + alpha, | |
| colorHash[key][4] + 1 | |
| ] | |
| } | |
| else { | |
| colorHash[key] = [red * alpha, green * alpha, blue * alpha, alpha, 1] | |
| } | |
| if (max[4] < colorHash[key][4]) { | |
| max = colorHash[key] | |
| } | |
| } | |
| let redTotal = max[0] | |
| let greenTotal = max[1] | |
| let blueTotal = max[2] | |
| let alphaTotal = max[3] | |
| let count = max[4] | |
| return alphaTotal ? [ | |
| Math.round(redTotal / alphaTotal), | |
| Math.round(greenTotal / alphaTotal), | |
| Math.round(blueTotal / alphaTotal), | |
| Math.round(alphaTotal / count) | |
| ] : options.defaultColor | |
| } | |
| function simpleAlgorithm(arr, len, options) { | |
| let redTotal = 0 | |
| let greenTotal = 0 | |
| let blueTotal = 0 | |
| let alphaTotal = 0 | |
| let count = 0 | |
| let ignoredColor = options.ignoredColor | |
| let step = options.step | |
| for (let i = 0; i < len; i += step) { | |
| let alpha = arr[i + 3] | |
| let red = arr[i] * alpha | |
| let green = arr[i + 1] * alpha | |
| let blue = arr[i + 2] * alpha | |
| if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) { | |
| continue | |
| } | |
| redTotal += red | |
| greenTotal += green | |
| blueTotal += blue | |
| alphaTotal += alpha | |
| count++ | |
| } | |
| return alphaTotal ? [ | |
| Math.round(redTotal / alphaTotal), | |
| Math.round(greenTotal / alphaTotal), | |
| Math.round(blueTotal / alphaTotal), | |
| Math.round(alphaTotal / count) | |
| ] : options.defaultColor | |
| } | |
| function sqrtAlgorithm(arr, len, options) { | |
| let redTotal = 0 | |
| let greenTotal = 0 | |
| let blueTotal = 0 | |
| let alphaTotal = 0 | |
| let count = 0 | |
| let ignoredColor = options.ignoredColor | |
| let step = options.step | |
| for (let i = 0; i < len; i += step) { | |
| let red = arr[i] | |
| let green = arr[i + 1] | |
| let blue = arr[i + 2] | |
| let alpha = arr[i + 3] | |
| if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) { | |
| continue | |
| } | |
| redTotal += red * red * alpha | |
| greenTotal += green * green * alpha | |
| blueTotal += blue * blue * alpha | |
| alphaTotal += alpha | |
| count++ | |
| } | |
| return alphaTotal ? [ | |
| Math.round(Math.sqrt(redTotal / alphaTotal)), | |
| Math.round(Math.sqrt(greenTotal / alphaTotal)), | |
| Math.round(Math.sqrt(blueTotal / alphaTotal)), | |
| Math.round(alphaTotal / count) | |
| ] : options.defaultColor | |
| } | |
| function getDefaultColor(options) { | |
| return getOption(options, 'defaultColor', [0, 0, 0, 0]) | |
| } | |
| function getOption(options, name, defaultValue) { | |
| return (options[name] === undefined ? defaultValue : options[name]) | |
| } | |
| let MIN_SIZE = 10 | |
| let MAX_SIZE = 100 | |
| function isSvg(filename) { | |
| return filename.search(/\.svg(\?|$)/i) !== -1 | |
| } | |
| function getOriginalSize(resource) { | |
| if (isInstanceOfHTMLImageElement(resource)) { | |
| let width = resource.naturalWidth | |
| let height = resource.naturalHeight | |
| // For SVG images with only viewBox attribute | |
| if (!resource.naturalWidth && isSvg(resource.src)) { | |
| width = height = MAX_SIZE | |
| } | |
| return { | |
| width: width, | |
| height: height, | |
| } | |
| } | |
| if (isInstanceOfHTMLVideoElement(resource)) { | |
| return { | |
| width: resource.videoWidth, | |
| height: resource.videoHeight | |
| } | |
| } | |
| return { | |
| width: resource.width, | |
| height: resource.height | |
| } | |
| } | |
| function getSrc(resource) { | |
| if (isInstanceOfHTMLCanvasElement(resource)) { | |
| return 'canvas' | |
| } | |
| if (isInstanceOfOffscreenCanvas(resource)) { | |
| return 'offscreencanvas' | |
| } | |
| if (isInstanceOfImageBitmap(resource)) { | |
| return 'imagebitmap' | |
| } | |
| return resource.src | |
| } | |
| function isInstanceOfHTMLImageElement(resource) { | |
| return typeof HTMLImageElement !== 'undefined' && resource instanceof HTMLImageElement | |
| } | |
| let hasOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' | |
| function isInstanceOfOffscreenCanvas(resource) { | |
| return hasOffscreenCanvas && resource instanceof OffscreenCanvas | |
| } | |
| function isInstanceOfHTMLVideoElement(resource) { | |
| return typeof HTMLVideoElement !== 'undefined' && resource instanceof HTMLVideoElement | |
| } | |
| function isInstanceOfHTMLCanvasElement(resource) { | |
| return typeof HTMLCanvasElement !== 'undefined' && resource instanceof HTMLCanvasElement | |
| } | |
| function isInstanceOfImageBitmap(resource) { | |
| return typeof ImageBitmap !== 'undefined' && resource instanceof ImageBitmap | |
| } | |
| function prepareSizeAndPosition(originalSize, options) { | |
| let srcLeft = getOption(options, 'left', 0) | |
| let srcTop = getOption(options, 'top', 0) | |
| let srcWidth = getOption(options, 'width', originalSize.width) | |
| let srcHeight = getOption(options, 'height', originalSize.height) | |
| let destWidth = srcWidth | |
| let destHeight = srcHeight | |
| if (options.mode === 'precision') { | |
| return { | |
| srcLeft: srcLeft, | |
| srcTop: srcTop, | |
| srcWidth: srcWidth, | |
| srcHeight: srcHeight, | |
| destWidth: destWidth, | |
| destHeight: destHeight | |
| } | |
| } | |
| let factor | |
| if (srcWidth > srcHeight) { | |
| factor = srcWidth / srcHeight | |
| destWidth = MAX_SIZE | |
| destHeight = Math.round(destWidth / factor) | |
| } | |
| else { | |
| factor = srcHeight / srcWidth | |
| destHeight = MAX_SIZE | |
| destWidth = Math.round(destHeight / factor) | |
| } | |
| if (destWidth > srcWidth || destHeight > srcHeight || | |
| destWidth < MIN_SIZE || destHeight < MIN_SIZE) { | |
| destWidth = srcWidth | |
| destHeight = srcHeight | |
| } | |
| return { | |
| srcLeft: srcLeft, | |
| srcTop: srcTop, | |
| srcWidth: srcWidth, | |
| srcHeight: srcHeight, | |
| destWidth: destWidth, | |
| destHeight: destHeight | |
| } | |
| } | |
| let isWebWorkers = typeof window === 'undefined' | |
| function makeCanvas() { | |
| if (isWebWorkers) { | |
| return hasOffscreenCanvas ? new OffscreenCanvas(1, 1) : null | |
| } | |
| return document.createElement('canvas') | |
| } | |
| let ERROR_PREFIX = 'FastAverageColor: ' | |
| function getError(message) { | |
| return Error(ERROR_PREFIX + message) | |
| } | |
| function outputError(error, silent) { | |
| if (!silent) { | |
| console.error(error) | |
| } | |
| } | |
| let FastAverageColor = /** @class */ (function () { | |
| function FastAverageColor() { | |
| this.canvas = null | |
| this.ctx = null | |
| } | |
| /** | |
| * Get asynchronously the average color from not loaded image. | |
| */ | |
| FastAverageColor.prototype.getColorAsync = function (resource, options) { | |
| if (!resource) { | |
| return Promise.reject(getError('call .getColorAsync() without resource.')) | |
| } | |
| if (typeof resource === 'string') { | |
| // Web workers | |
| if (typeof Image === 'undefined') { | |
| return Promise.reject(getError('resource as string is not supported in this environment')) | |
| } | |
| let img = new Image() | |
| img.crossOrigin = options && options.crossOrigin || '' | |
| img.src = resource | |
| return this.bindImageEvents(img, options) | |
| } | |
| else if (isInstanceOfHTMLImageElement(resource) && !resource.complete) { | |
| return this.bindImageEvents(resource, options) | |
| } | |
| else { | |
| let result = this.getColor(resource, options) | |
| return result.error ? Promise.reject(result.error) : Promise.resolve(result) | |
| } | |
| } | |
| /** | |
| * Get the average color from images, videos and canvas. | |
| */ | |
| FastAverageColor.prototype.getColor = function (resource, options) { | |
| options = options || {} | |
| let defaultColor = getDefaultColor(options) | |
| if (!resource) { | |
| let error = getError('call .getColor(null) without resource') | |
| outputError(error, options.silent) | |
| return this.prepareResult(defaultColor, error) | |
| } | |
| let originalSize = getOriginalSize(resource) | |
| let size = prepareSizeAndPosition(originalSize, options) | |
| if (!size.srcWidth || !size.srcHeight || !size.destWidth || !size.destHeight) { | |
| let error = getError("incorrect sizes for resource \"".concat(getSrc(resource), "\"")) | |
| outputError(error, options.silent) | |
| return this.prepareResult(defaultColor, error) | |
| } | |
| if (!this.canvas) { | |
| this.canvas = makeCanvas() | |
| if (!this.canvas) { | |
| let error = getError('OffscreenCanvas is not supported in this browser') | |
| outputError(error, options.silent) | |
| return this.prepareResult(defaultColor, error) | |
| } | |
| } | |
| if (!this.ctx) { | |
| this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }) | |
| if (!this.ctx) { | |
| let error = getError('Canvas Context 2D is not supported in this browser') | |
| outputError(error, options.silent) | |
| return this.prepareResult(defaultColor) | |
| } | |
| this.ctx.imageSmoothingEnabled = false | |
| } | |
| this.canvas.width = size.destWidth | |
| this.canvas.height = size.destHeight | |
| try { | |
| this.ctx.clearRect(0, 0, size.destWidth, size.destHeight) | |
| this.ctx.drawImage(resource, size.srcLeft, size.srcTop, size.srcWidth, size.srcHeight, 0, 0, size.destWidth, size.destHeight) | |
| let bitmapData = this.ctx.getImageData(0, 0, size.destWidth, size.destHeight).data | |
| return this.prepareResult(this.getColorFromArray4(bitmapData, options)) | |
| } | |
| catch (originalError) { | |
| let error = getError("security error (CORS) for resource ".concat(getSrc(resource), ".\nDetails: https://developer.mozilla.org/en/docs/Web/HTML/CORS_enabled_image")) | |
| outputError(error, options.silent) | |
| !options.silent && console.error(originalError) | |
| return this.prepareResult(defaultColor, error) | |
| } | |
| } | |
| /** | |
| * Get the average color from a array when 1 pixel is 4 bytes. | |
| */ | |
| FastAverageColor.prototype.getColorFromArray4 = function (arr, options) { | |
| options = options || {} | |
| let bytesPerPixel = 4 | |
| let arrLength = arr.length | |
| let defaultColor = getDefaultColor(options) | |
| if (arrLength < bytesPerPixel) { | |
| return defaultColor | |
| } | |
| let len = arrLength - arrLength % bytesPerPixel | |
| let step = (options.step || 1) * bytesPerPixel | |
| let algorithm | |
| switch (options.algorithm || 'sqrt') { | |
| case 'simple': | |
| algorithm = simpleAlgorithm | |
| break | |
| case 'sqrt': | |
| algorithm = sqrtAlgorithm | |
| break | |
| case 'dominant': | |
| algorithm = dominantAlgorithm | |
| break | |
| default: | |
| throw getError("".concat(options.algorithm, " is unknown algorithm")) | |
| } | |
| return algorithm(arr, len, { | |
| defaultColor: defaultColor, | |
| ignoredColor: prepareIgnoredColor(options.ignoredColor), | |
| step: step | |
| }) | |
| } | |
| /** | |
| * Get color data from value ([r, g, b, a]). | |
| */ | |
| FastAverageColor.prototype.prepareResult = function (value, error) { | |
| let rgb = value.slice(0, 3) | |
| let rgba = [value[0], value[1], value[2], value[3] / 255] | |
| let isDarkColor = isDark(value) | |
| return { | |
| value: [value[0], value[1], value[2], value[3]], | |
| rgb: 'rgb(' + rgb.join(',') + ')', | |
| rgba: 'rgba(' + rgba.join(',') + ')', | |
| hex: arrayToHex(rgb), | |
| hexa: arrayToHex(value), | |
| isDark: isDarkColor, | |
| isLight: !isDarkColor, | |
| error: error, | |
| } | |
| } | |
| /** | |
| * Destroy the instance. | |
| */ | |
| FastAverageColor.prototype.destroy = function () { | |
| if (this.canvas) { | |
| this.canvas.width = 1 | |
| this.canvas.height = 1 | |
| this.canvas = null | |
| } | |
| this.ctx = null | |
| } | |
| FastAverageColor.prototype.bindImageEvents = function (resource, options) { | |
| let _this = this | |
| return new Promise(function (resolve, reject) { | |
| let onload = function () { | |
| unbindEvents() | |
| let result = _this.getColor(resource, options) | |
| if (result.error) { | |
| reject(result.error) | |
| } | |
| else { | |
| resolve(result) | |
| } | |
| } | |
| let onerror = function () { | |
| unbindEvents() | |
| reject(getError("Error loading image \"".concat(resource.src, "\"."))) | |
| } | |
| let onabort = function () { | |
| unbindEvents() | |
| reject(getError("Image \"".concat(resource.src, "\" loading aborted"))) | |
| } | |
| let unbindEvents = function () { | |
| resource.removeEventListener('load', onload) | |
| resource.removeEventListener('error', onerror) | |
| resource.removeEventListener('abort', onabort) | |
| } | |
| resource.addEventListener('load', onload) | |
| resource.addEventListener('error', onerror) | |
| resource.addEventListener('abort', onabort) | |
| }) | |
| } | |
| return FastAverageColor | |
| }()) | |
| export default FastAverageColor |