This commit is contained in:
2024-06-28 13:32:22 +02:00
parent 18cad78969
commit bfbb5d64de
34 changed files with 8132 additions and 739 deletions

2
.gitignore vendored
View File

@@ -16,3 +16,5 @@ rsync-exclude
node_modules node_modules
.idea .idea
built

View File

@@ -1,5 +1,6 @@
var fs = require('fs'); /* eslint-disable no-undef */
const { resolve } = require('path'); import { copyFile } from 'fs';
import path from 'path';
/* /*
color-thief.umd.js duplicated as color-thief.min.js for legacy support color-thief.umd.js duplicated as color-thief.min.js for legacy support
@@ -17,20 +18,20 @@ duplicating the UMD compatible file and giving it that name.
const umdRelPath = 'dist/color-thief.umd.js'; const umdRelPath = 'dist/color-thief.umd.js';
const legacyRelPath = 'dist/color-thief.min.js'; const legacyRelPath = 'dist/color-thief.min.js';
const umdPath = resolve(process.cwd(), umdRelPath); const umdPath = path.join( path.resolve( path.dirname( '' ) ), umdRelPath );
const legacyPath = resolve(process.cwd(), legacyRelPath); const legacyPath = path.join( path.resolve( path.dirname( '' ) ), legacyRelPath );
fs.copyFile(umdPath, legacyPath, (err) => { copyFile( umdPath, legacyPath, ( err ) => {
if (err) throw err; if ( err ) throw err;
console.log(`${umdRelPath} copied to ${legacyRelPath}.`); console.log( `${ umdRelPath } copied to ${ legacyRelPath }.` );
}); } );
const srcNodeRelPath = 'src/color-thief-node.js'; const srcNodeRelPath = 'built/color-thief-node.js';
const distNodeRelPath = 'dist/color-thief.js'; const distNodeRelPath = 'dist/color-thief.js';
const srcNodePath = resolve(process.cwd(), srcNodeRelPath); const srcNodePath = path.join( path.resolve( path.dirname( '' ) ), srcNodeRelPath );
const distNodePath = resolve(process.cwd(), distNodeRelPath); const distNodePath = path.join( path.resolve( path.dirname( '' ) ), distNodeRelPath );
fs.copyFile(srcNodePath, distNodePath, (err) => { copyFile( srcNodePath, distNodePath, ( err ) => {
if (err) throw err; if ( err ) throw err;
console.log(`${srcNodeRelPath} copied to ${distNodeRelPath}.`); console.log( `${ srcNodeRelPath } copied to ${ distNodeRelPath }.` );
}); } );

15
cypress.config.cjs Normal file
View File

@@ -0,0 +1,15 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
const { defineConfig } = require( 'cypress' );
module.exports = defineConfig( {
'e2e': {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents ( on, config ) {
return require( './cypress/plugins/index.cjs' )( on, config );
},
'experimentalRunAllSpecs': true,
},
} );

View File

@@ -1,13 +0,0 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
experimentalRunAllSpecs: true,
},
})

View File

@@ -28,7 +28,7 @@
if (img.complete) { if (img.complete) {
getColorFromImage(img); getColorFromImage(img);
} else { } else {
image.addEventListener('load', function() { img.addEventListener('load', function() {
getColorFromImage(img); getColorFromImage(img);
}); });
} }

View File

@@ -11,49 +11,58 @@ var images = [
]; ];
// Render example images // Render example images
var examplesHTML = Mustache.to_html(document.getElementById('image-tpl').innerHTML, images); console.log( Mustache );
document.getElementById('example-images').innerHTML = examplesHTML; var examplesHTML = Mustache.render( document.getElementById( 'image-tpl' ).innerHTML, images );
document.getElementById( 'example-images' ).innerHTML = examplesHTML;
// Once images are loaded, process them // Once images are loaded, process them
document.querySelectorAll('.image').forEach((image) => { document.querySelectorAll( '.image' ).forEach( ( image ) => {
const section = image.closest('.image-section'); const section = image.closest( '.image-section' );
if (this.complete) { if ( this.complete ) {
showColorsForImage(image, section); showColorsForImage( image, section );
} else { } else {
image.addEventListener('load', function() { image.addEventListener( 'load', function () {
showColorsForImage(image, section); showColorsForImage( image, section );
}); // showColorsForImageURLNewAPI( )
} );
} }
}) } );
// Run Color Thief functions and display results below image. // Run Color Thief functions and display results below image.
// We also log execution time of functions for display. // We also log execution time of functions for display.
const showColorsForImage = function(image, section) { const showColorsForImage = function ( image, section ) {
// getColor(img) // getColor(img)
let start = Date.now(); let start = Date.now();
let result = colorThief.getColor(image); let result = colorThief.getColor( image );
let elapsedTime = Date.now() - start; let elapsedTime = Date.now() - start;
const colorHTML = Mustache.to_html(document.getElementById('color-tpl').innerHTML, { const colorHTML = Mustache.render( document.getElementById( 'color-tpl' ).innerHTML, {
color: result, 'color': result,
colorStr: result.toString(), 'colorStr': result.toString(),
elapsedTime elapsedTime
}) } );
// getPalette(img) // getPalette(img)
let paletteHTML = ''; let paletteHTML = '';
let colorCounts = [2, 3, 5, 7, 10, 20]; let colorCounts = [
colorCounts.forEach((count) => { 2,
3,
5,
7,
10,
20
];
colorCounts.forEach( ( count ) => {
let start = Date.now(); let start = Date.now();
let result = colorThief.getPalette(image, count); let result = colorThief.getPalette( image, count );
let elapsedTime = Date.now() - start; let elapsedTime = Date.now() - start;
paletteHTML += Mustache.to_html(document.getElementById('palette-tpl').innerHTML, { paletteHTML += Mustache.render( document.getElementById( 'palette-tpl' ).innerHTML, {
count, count,
palette: result, 'palette': result,
paletteStr: result.toString(), 'paletteStr': result.toString(),
elapsedTime elapsedTime
}) } );
}); } );
const outputEl = section.querySelector('.output'); const outputEl = section.querySelector( '.output' );
outputEl.innerHTML += colorHTML + paletteHTML; outputEl.innerHTML += colorHTML + paletteHTML;
}; };

1
dist/color-thief-node.cjs vendored Normal file
View File

@@ -0,0 +1 @@
var t=require("ndarray-pixels"),e=require("@lokesh.dhakar/quantize"),n=require("sharp");function r(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=/*#__PURE__*/r(e),u=/*#__PURE__*/r(n),a=function(e,n,r){void 0===n&&(n=10),void 0===r&&(r=10);var a=function(t){var e=t.colorCount,n=t.quality;if(void 0!==e&&Number.isInteger(e)){if(1===e)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");e=Math.max(e,2),e=Math.min(e,20)}else e=10;return(void 0===n||!Number.isInteger(n)||n<1)&&(n=10),{colorCount:e,quality:n}}({colorCount:n,quality:r});return new Promise(function(n,r){(function(e){return new Promise(function(n,r){u.default(e).toBuffer().then(function(t){return u.default(t).metadata().then(function(e){return{buffer:t,format:e.format}})}).then(function(e){return t.getPixels(e.buffer,e.format)}).then(n).catch(r)})})(e).then(function(t){var e=t.shape[0]*t.shape[1],r=function(t,e,n){for(var r=[],o=0;o<e;o+=n){var u=4*o,a=t[u+0],i=t[u+1],f=t[u+2],c=t[u+3];(void 0===c||c>=125)&&(a>250&&i>250&&f>250||r.push([a,i,f]))}return r}(Array.from(t.data),e,a.quality),u=o.default(r,a.colorCount),i=u?u.palette():null;n(i)}).catch(function(t){r(t)})})};module.exports={getColor:function(t,e){return void 0===e&&(e=10),new Promise(function(n,r){a(t,5,e).then(function(t){n(t[0])}).catch(function(t){r(t)})})},getPalette:a};

1
dist/color-thief-node.mjs vendored Normal file
View File

@@ -0,0 +1 @@
import{getPixels as t}from"ndarray-pixels";import o from"@lokesh.dhakar/quantize";import n from"sharp";var r=function(r,e,u){void 0===e&&(e=10),void 0===u&&(u=10);var a=function(t){var o=t.colorCount,n=t.quality;if(void 0!==o&&Number.isInteger(o)){if(1===o)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");o=Math.max(o,2),o=Math.min(o,20)}else o=10;return(void 0===n||!Number.isInteger(n)||n<1)&&(n=10),{colorCount:o,quality:n}}({colorCount:e,quality:u});return new Promise(function(e,u){(function(o){return new Promise(function(r,e){n(o).toBuffer().then(function(t){return n(t).metadata().then(function(o){return{buffer:t,format:o.format}})}).then(function(o){return t(o.buffer,o.format)}).then(r).catch(e)})})(r).then(function(t){var n=t.shape[0]*t.shape[1],r=function(t,o,n){for(var r=[],e=0;e<o;e+=n){var u=4*e,a=t[u+0],i=t[u+1],f=t[u+2],c=t[u+3];(void 0===c||c>=125)&&(a>250&&i>250&&f>250||r.push([a,i,f]))}return r}(Array.from(t.data),n,a.quality),u=o(r,a.colorCount),i=u?u.palette():null;e(i)}).catch(function(t){u(t)})})};module.exports={getColor:function(t,o){return void 0===o&&(o=10),new Promise(function(n,e){r(t,5,o).then(function(t){n(t[0])}).catch(function(t){e(t)})})},getPalette:r};

1
dist/color-thief-node.modern.js vendored Normal file
View File

@@ -0,0 +1 @@
import{getPixels as t}from"ndarray-pixels";import e from"@lokesh.dhakar/quantize";import o from"sharp";const r=(r,a=10,n=10)=>{const l=(t=>{let e=t.colorCount,o=t.quality;if(void 0!==e&&Number.isInteger(e)){if(1===e)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");e=Math.max(e,2),e=Math.min(e,20)}else e=10;return(void 0===o||!Number.isInteger(o)||o<1)&&(o=10),{colorCount:e,quality:o}})({colorCount:a,quality:n});return new Promise((a,n)=>{(e=>new Promise((r,a)=>{o(e).toBuffer().then(t=>o(t).metadata().then(e=>({buffer:t,format:e.format}))).then(({buffer:e,format:o})=>t(e,o)).then(r).catch(a)}))(r).then(t=>{const o=t.shape[0]*t.shape[1],r=((t,e,o)=>{const r=[];for(let a=0;a<e;a+=o){const e=4*a,o=t[e+0],n=t[e+1],l=t[e+2],u=t[e+3];(void 0===u||u>=125)&&(o>250&&n>250&&l>250||r.push([o,n,l]))}return r})(Array.from(t.data),o,l.quality),n=e(r,l.colorCount),u=n?n.palette():null;a(u)}).catch(t=>{n(t)})})};module.exports={getColor:(t,e=10)=>new Promise((o,a)=>{r(t,5,e).then(t=>{o(t[0])}).catch(t=>{a(t)})}),getPalette:r};

1
dist/color-thief-node.umd.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("ndarray-pixels"),require("@lokesh.dhakar/quantize"),require("sharp")):"function"==typeof define&&define.amd?define(["ndarray-pixels","@lokesh.dhakar/quantize","sharp"],t):t((e||self).ndarrayPixels,e.quantize,e.sharp)}(this,function(e,t,n){function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=/*#__PURE__*/o(t),u=/*#__PURE__*/o(n),a=function(t,n,o){void 0===n&&(n=10),void 0===o&&(o=10);var a=function(e){var t=e.colorCount,n=e.quality;if(void 0!==t&&Number.isInteger(t)){if(1===t)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");t=Math.max(t,2),t=Math.min(t,20)}else t=10;return(void 0===n||!Number.isInteger(n)||n<1)&&(n=10),{colorCount:t,quality:n}}({colorCount:n,quality:o});return new Promise(function(n,o){(function(t){return new Promise(function(n,o){u.default(t).toBuffer().then(function(e){return u.default(e).metadata().then(function(t){return{buffer:e,format:t.format}})}).then(function(t){return e.getPixels(t.buffer,t.format)}).then(n).catch(o)})})(t).then(function(e){var t=e.shape[0]*e.shape[1],o=function(e,t,n){for(var o=[],r=0;r<t;r+=n){var u=4*r,a=e[u+0],i=e[u+1],f=e[u+2],l=e[u+3];(void 0===l||l>=125)&&(a>250&&i>250&&f>250||o.push([a,i,f]))}return o}(Array.from(e.data),t,a.quality),u=r.default(o,a.colorCount),i=u?u.palette():null;n(i)}).catch(function(e){o(e)})})};module.exports={getColor:function(e,t){return void 0===t&&(t=10),new Promise(function(n,o){a(e,5,t).then(function(e){n(e[0])}).catch(function(e){o(e)})})},getPalette:a}});

1
dist/color-thief.cjs vendored Normal file

File diff suppressed because one or more lines are too long

158
dist/color-thief.js vendored
View File

@@ -1,17 +1,40 @@
const getPixels = require('get-pixels'); /**
const quantize = require('@lokesh.dhakar/quantize'); * Color Thief Node v3.0.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com
function createPixelArray(imgData, pixelCount, quality) { *
const pixels = imgData; * Thanks
* ------
* Nick Rabinowitz - For creating quantize.js.
* John Schulz - For clean up and optimization. @JFSIII
* Nathan Spady - For adding drag and drop support to the demo page.
*
* License
* -------
* Copyright Lokesh Dhakar
* Released under the MIT license
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
*
* @license MIT
*/
// Thanks to this PR for the work of migrating to ndarray-pixels pr https://github.com/lokesh/color-thief/pull/254
import { getPixels } from 'ndarray-pixels';
import quantize from '@lokesh.dhakar/quantize';
import sharp from 'sharp';
/**
* Create an array of arrays of pixels from an array of pixels
* @param {number[]} pixels An array of pixels
* @param {number} pixelCount The total number of pixels
* @param {number} quality
* @returns {number[][]} Returns an array of arrays of pixel values ([ r, g, b ])
*/
const createPixelArray = (pixels, pixelCount, quality) => {
const pixelArray = []; const pixelArray = [];
for (let i = 0; i < pixelCount; i = i + quality) {
for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) { const offset = i * 4;
offset = i * 4; const r = pixels[offset + 0];
r = pixels[offset + 0]; const g = pixels[offset + 1];
g = pixels[offset + 1]; const b = pixels[offset + 2];
b = pixels[offset + 2]; const a = pixels[offset + 3];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white // If pixel is mostly opaque and not white
if (typeof a === 'undefined' || a >= 125) { if (typeof a === 'undefined' || a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) { if (!(r > 250 && g > 250 && b > 250)) {
@@ -20,80 +43,99 @@ function createPixelArray(imgData, pixelCount, quality) {
} }
} }
return pixelArray; return pixelArray;
} };
/**
function validateOptions(options) { * Validate Color-Thief options
let { colorCount, quality } = options; * @param {{ colorCount: number; quality: number; }} options The options object
* @returns {{ colorCount: number, quality: number }} The same object, but validated
*/
const validateOptions = (options) => {
let colorCount = options.colorCount;
let quality = options.quality;
if (typeof colorCount === 'undefined' || !Number.isInteger(colorCount)) { if (typeof colorCount === 'undefined' || !Number.isInteger(colorCount)) {
colorCount = 10; colorCount = 10;
} else if (colorCount === 1 ) { }
else if (colorCount === 1) {
throw new Error('colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()'); throw new Error('colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()');
} else { }
else {
colorCount = Math.max(colorCount, 2); colorCount = Math.max(colorCount, 2);
colorCount = Math.min(colorCount, 20); colorCount = Math.min(colorCount, 20);
} }
if (typeof quality === 'undefined' ||
if (typeof quality === 'undefined' || !Number.isInteger(quality) || quality < 1) { !Number.isInteger(quality) ||
quality < 1) {
quality = 10; quality = 10;
} }
return { return {
colorCount, colorCount,
quality quality,
} };
} };
/**
function loadImg(img) { * Load an image from the disk an pre-process
* @param {string} img Path to the image on the disk
* @returns {Promise<ndarray.NdArray<Uint8Array>>} Returns the pre-processed image
*/
const loadImg = (img) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getPixels(img, function(err, data) { sharp(img)
if(err) { .toBuffer()
reject(err) .then((buffer) => sharp(buffer).metadata()
} else { .then((metadata) => ({ buffer, 'format': metadata.format })))
resolve(data); .then(({ buffer, format }) => getPixels(buffer, format))
} .then(resolve)
}) .catch(reject);
}); });
} };
/**
function getColor(img, quality) { * Get the dominant color of an image
* @param {string} img Path to the image on the disk
* @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {Promise<ColorThiefResult>} Returns the dominant color
*/
const getColor = (img, quality = 10) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getPalette(img, 5, quality) getPalette(img, 5, quality)
.then(palette => { .then((palette) => {
resolve(palette[0]); resolve(palette[0]);
}) })
.catch(err => { .catch((err) => {
reject(err); reject(err);
})
}); });
});
} };
/**
function getPalette(img, colorCount = 10, quality = 10) { * Get the color palette of an image
* @param {string} img Path to the image on the disk
* @param {number?} colorCount (Optional) the target amount of colors to try and extract
* @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {Promise<ColorThiefResult[]>} Returns an array of colors
*/
const getPalette = (img, colorCount = 10, quality = 10) => {
const options = validateOptions({ const options = validateOptions({
colorCount, colorCount,
quality quality,
}); });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
loadImg(img) loadImg(img)
.then(imgData => { .then((imgData) => {
const pixelCount = imgData.shape[0] * imgData.shape[1]; const pixelCount = imgData.shape[0] * imgData.shape[1];
const pixelArray = createPixelArray(imgData.data, pixelCount, options.quality); const pixelArray = createPixelArray(Array.from(imgData.data), pixelCount, options.quality);
const cmap = quantize(pixelArray, options.colorCount); const cmap = quantize(pixelArray, options.colorCount);
const palette = cmap? cmap.palette() : null; const palette = cmap ? cmap.palette() : null;
resolve(palette); resolve(palette);
}) })
.catch(err => { .catch((err) => {
reject(err); reject(err);
})
}); });
} });
};
module.exports = { module.exports = {
getColor, getColor,
getPalette getPalette,
}; };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/color-thief.modern.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"version":3,"file":"color-thief.umd.js","sources":["../build/build.js"],"sourcesContent":["var fs = require('fs');\r\nconst { resolve } = require('path');\r\n\r\n/*\r\ncolor-thief.umd.js duplicated as color-thief.min.js for legacy support\r\n\r\nIn Color Thief v2.1 <= there was one distribution file (dist/color-thief.min.js)\r\nand it exposed a global variable ColorThief. Starting from v2.2, the package\r\nincludes multiple dist files for the various module systems. One of these is\r\nthe UMD format which falls back to a global variable if the requirejs AMD format\r\nis not being used. This file is called color-thief.umd.js in the dist folder. We\r\nwant to keep supporting the previous users who were loading\r\ndist/color-thief.min.js and expecting a global var. For this reason we're\r\nduplicating the UMD compatible file and giving it that name.\r\n*/\r\n\r\nconst umdRelPath = 'dist/color-thief.umd.js';\r\nconst legacyRelPath = 'dist/color-thief.min.js';\r\n\r\nconst umdPath = resolve(process.cwd(), umdRelPath);\r\nconst legacyPath = resolve(process.cwd(), legacyRelPath);\r\n\r\nfs.copyFile(umdPath, legacyPath, (err) => {\r\n if (err) throw err;\r\n console.log(`${umdRelPath} copied to ${legacyRelPath}.`);\r\n});\r\n\r\nconst srcNodeRelPath = 'src/color-thief-node.js';\r\nconst distNodeRelPath = 'dist/color-thief.js';\r\nconst srcNodePath = resolve(process.cwd(), srcNodeRelPath);\r\nconst distNodePath = resolve(process.cwd(), distNodeRelPath);\r\n\r\nfs.copyFile(srcNodePath, distNodePath, (err) => {\r\n if (err) throw err;\r\n console.log(`${srcNodeRelPath} copied to ${distNodeRelPath}.`);\r\n});\r\n"],"names":["fs","require","umdPath","resolve","process","cwd","legacyPath","copyFile","err","console","log","const","srcNodePath","distNodePath"],"mappings":"0IAAA,IAAIA,EAAKC,QAAQ,QACGA,QAAQ,gBAkBtBC,EAAUC,EAAQC,QAAQC,MAHb,2BAIbC,EAAaH,EAAQC,QAAQC,MAHb,2BAKtBL,EAAGO,SAASL,EAASI,WAAaE,MAC1BA,EAAK,MAAMA,EACfC,QAAQC,oEAGZC,IAEMC,EAAcT,EAAQC,QAAQC,MAFb,2BAGjBQ,EAAeV,EAAQC,QAAQC,MAFb,uBAIxBL,EAAGO,SAASK,EAAaC,WAAeL,MAChCA,EAAK,MAAMA,EACfC,QAAQC"}

1
dist/core.cjs vendored Normal file
View File

@@ -0,0 +1 @@
module.exports={createPixelArray:function(e,o,r){for(var t=[],n=0;n<o;n+=r){var a=4*n,i=e[a+0],l=e[a+1],u=e[a+2],c=e[a+3];(void 0===c||c>=125)&&(i>250&&l>250&&u>250||t.push([i,l,u]))}return t},validateOptions:function(e){var o=e.colorCount,r=e.quality;if(void 0!==o&&Number.isInteger(o)){if(1===o)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");o=Math.max(o,2),o=Math.min(o,20)}else o=10;return(void 0===r||!Number.isInteger(r)||r<1)&&(r=10),{colorCount:o,quality:r}}};

1
dist/core.mjs vendored Normal file
View File

@@ -0,0 +1 @@
var e={createPixelArray:function(e,o,r){for(var t=[],a=0;a<o;a+=r){var n=4*a,i=e[n+0],l=e[n+1],u=e[n+2],c=e[n+3];(void 0===c||c>=125)&&(i>250&&l>250&&u>250||t.push([i,l,u]))}return t},validateOptions:function(e){var o=e.colorCount,r=e.quality;if(void 0!==o&&Number.isInteger(o)){if(1===o)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");o=Math.max(o,2),o=Math.min(o,20)}else o=10;return(void 0===r||!Number.isInteger(r)||r<1)&&(r=10),{colorCount:o,quality:r}}};export{e as default};

1
dist/core.modern.js vendored Normal file
View File

@@ -0,0 +1 @@
var e={createPixelArray:(e,t,o)=>{const r=[];for(let l=0;l<t;l+=o){const t=4*l,o=e[t+0],a=e[t+1],n=e[t+2],i=e[t+3];(void 0===i||i>=125)&&(o>250&&a>250&&n>250||r.push([o,a,n]))}return r},validateOptions:e=>{let t=e.colorCount,o=e.quality;if(void 0!==t&&Number.isInteger(t)){if(1===t)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");t=Math.max(t,2),t=Math.min(t,20)}else t=10;return(void 0===o||!Number.isInteger(o)||o<1)&&(o=10),{colorCount:t,quality:o}}};export{e as default};

1
dist/core.umd.js vendored Normal file
View File

@@ -0,0 +1 @@
!function(e,o){"object"==typeof exports&&"undefined"!=typeof module?module.exports=o():"function"==typeof define&&define.amd?define(o):(e||self).ColorThief=o()}(this,function(){return{createPixelArray:function(e,o,t){for(var n=[],r=0;r<o;r+=t){var i=4*r,l=e[i+0],u=e[i+1],f=e[i+2],a=e[i+3];(void 0===a||a>=125)&&(l>250&&u>250&&f>250||n.push([l,u,f]))}return n},validateOptions:function(e){var o=e.colorCount,t=e.quality;if(void 0!==o&&Number.isInteger(o)){if(1===o)throw new Error("colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()");o=Math.max(o,2),o=Math.min(o,20)}else o=10;return(void 0===t||!Number.isInteger(t)||t<1)&&(t=10),{colorCount:o,quality:t}}}});

View File

@@ -1,75 +1,65 @@
import stylisticTS from '@stylistic/eslint-plugin'; // @ts-check
export default [ import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import stylistic from '@stylistic/eslint-plugin';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{ {
plugins: { 'plugins': {
'@stylistic/ts': stylisticTS, '@stylistic/js': stylistic,
'@stylistic/ts': stylistic,
}, },
languageOptions: { 'rules': {
ecmaVersion: 6,
sourceType: 'module',
},
rules: {
// Error prevention
'prefer-const': 'error',
'no-async-promise-executor': 'error',
'no-cond-assign': 'error',
'no-const-assign': 'error',
'no-constant-condition': 'error',
'no-debugger': 'error',
'no-dupe-else-if': 'error',
'no-dupe-keys': 'warn',
'no-dupe-class-members': 'error',
'no-dupe-args': 'error',
'no-duplicate-case': 'error',
'no-duplicate-imports': 'error',
'no-empty-pattern': 'error',
'no-fallthrough': 'error',
'no-invalid-regexp': 'warn',
'no-loss-of-precision': 'warn',
'no-import-assign': 'error',
'no-irregular-whitespace': 'error',
'no-self-assign': 'error',
'no-undef': 'warn',
'no-unreachable': 'error',
'no-unsafe-finally': 'error',
'no-unsafe-negation': 'error',
'no-unused-private-class-members': 'warn',
'no-unused-vars': 'error',
'no-use-before-define': 'error',
// Style
'arrow-body-style': [ 'error', 'always' ],
'camelcase': 'warn',
'curly': [ 'error', 'all' ],
'eqeqeq': [ 'error', 'always' ],
'func-style': [ 'error', 'expression' ],
'no-array-constructor': 'error',
'no-else-return': 'error',
'no-empty': 'warn',
'no-empty-static-block': 'warn',
'no-eval': 'error',
'no-implicit-coercion': 'error',
'no-invalid-this': 'warn',
'no-multi-assign': 'warn',
'no-new': 'error',
'no-redeclare': 'error',
'no-script-url': 'error',
'no-shadow': 'error',
'no-shadow-restricted-names': 'error',
'no-underscore-dangle': 'warn',
'no-unused-expressions': 'warn',
'no-useless-constructor': 'warn',
'no-var': 'warn',
'prefer-arrow-callback': 'error',
'prefer-exponentiation-operator': 'warn',
'prefer-numeric-literals': 'error',
'require-await': 'warn',
// Formatting // Formatting
// https://eslint.style/rules/js/array-bracket-newline '@stylistic/js/array-bracket-newline': [ 'error', { 'multiline': true, 'minItems': 4 } ],
// TODO: Finish up '@stylistic/js/array-bracket-spacing': [ 'error', 'always' ],
'@stylistic/ts/indent': [ 'error', 4 ] '@stylistic/js/array-element-newline': [ 'error', { 'multiline': true, 'minItems': 4 } ],
'@stylistic/js/arrow-parens': [ 'error', 'always' ],
'@stylistic/js/arrow-spacing': [ 'error', { 'before': true, 'after': true } ],
'@stylistic/js/block-spacing': [ 'error', 'always' ],
'@stylistic/js/brace-style': [ 'error', '1tbs' ],
'@stylistic/js/comma-spacing': [ 'error', { 'before': false, 'after': true } ],
'@stylistic/js/comma-style': [ 'error', 'last' ],
'@stylistic/js/dot-location': [ 'error', 'property' ],
'@stylistic/js/eol-last': [ 'error', 'always' ],
'@stylistic/js/function-call-spacing': [ 'error', 'never' ],
'@stylistic/js/implicit-arrow-linebreak': [ 'error', 'beside' ],
'@stylistic/js/indent': [ 'error', 4 ],
'@stylistic/js/key-spacing': [ 'error', { 'beforeColon': false, 'afterColon': true } ],
'@stylistic/js/keyword-spacing': [ 'error', { 'before': true, 'after': true } ],
'@stylistic/js/lines-between-class-members': [ 'error', 'always' ],
'@stylistic/js/new-parens': [ 'error', 'always' ],
'@stylistic/js/no-extra-parens': [ 'error', 'all' ],
'@stylistic/js/no-extra-semi': 'error',
'@stylistic/js/no-floating-decimal': 'error',
'@stylistic/js/no-mixed-operators': 'error',
'@stylistic/js/no-mixed-spaces-and-tabs': 'error',
'@stylistic/js/no-multi-spaces': 'error',
'@stylistic/js/no-trailing-spaces': 'error',
'@stylistic/js/no-whitespace-before-property': 'error',
'@stylistic/js/object-curly-newline': [ 'error', { 'multiline': true, 'minProperties': 3 } ],
'@stylistic/js/object-curly-spacing': [ 'error', 'always' ],
'@stylistic/js/one-var-declaration-per-line': 'error',
'@stylistic/js/quote-props': [ 'error', 'always' ],
'@stylistic/js/quotes': [ 'error', 'single' ],
'@stylistic/js/rest-spread-spacing': [ 'error', 'never' ],
'@stylistic/js/semi': [ 'error', 'always' ],
'@stylistic/js/semi-spacing': [ 'error', { 'before': false, 'after': true } ],
'@stylistic/js/semi-style': [ 'error', 'last' ],
'@stylistic/js/space-before-blocks': [ 'error', 'always' ],
'@stylistic/js/space-before-function-paren': [ 'error', 'always' ],
'@stylistic/js/space-in-parens': [ 'error', 'always' ],
'@stylistic/js/space-infix-ops': [ 'error', { 'int32Hint': false } ],
'@stylistic/js/space-unary-ops': 'error',
'@stylistic/js/spaced-comment': [ 'error', 'always' ],
'@stylistic/js/switch-colon-spacing': 'error',
'@stylistic/js/template-curly-spacing': [ 'error', 'always' ],
'@stylistic/js/wrap-iife': [ 'error', 'inside' ],
'@stylistic/js/wrap-regex': 'error',
'@stylistic/ts/type-annotation-spacing': 'error',
} }
} }
] );

7550
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,34 +16,50 @@
"photo", "photo",
"canvas" "canvas"
], ],
"type": "module",
"homepage": "http://lokeshdhakar.com/projects/color-thief/", "homepage": "http://lokeshdhakar.com/projects/color-thief/",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/lokesh/color-thief.git" "url": "https://github.com/lokesh/color-thief.git"
}, },
"license": "MIT", "license": "MIT",
"source": "src/color-thief.js", "source": "src/color-thief.ts",
"main": "dist/color-thief.js", "main": "dist/color-thief-node.cjs",
"module": "dist/color-thief.mjs", "browser": {
"dist/color-thief.mjs": "dist/color-thief.mjs",
"dist/color-thief.js": "dist/color-thief.js"
},
"module": "dist/color-thief-node.mjs",
"umd:main": "dist/color-thief.umd.js", "umd:main": "dist/color-thief.umd.js",
"amdName": "ColorThief", "amdName": "ColorThief",
"scripts": { "scripts": {
"build": "microbundle --no-sourcemap && node ./build/build.js", "build": "tsc --declaration && microbundle build --no-sourcemap built/*.js && node ./build/build.mjs",
"watch": "microbundle watch --no-sourcemap", "watch": "microbundle watch --no-sourcemap",
"dev": "http-server", "dev": "http-server",
"test": "mocha && cypress run --config video=false", "test": "mocha && cypress run --config video=false",
"test:browser": "./node_modules/.bin/cypress run --headed --browser chrome", "test:browser": "./node_modules/.bin/cypress run --headed",
"test:node": "mocha", "test:node": "mocha",
"cypress": "./node_modules/.bin/cypress open", "cypress": "./node_modules/.bin/cypress open",
"lint": "eslint ./src && prettier ./src" "lint": "eslint ./src/*"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.5.0",
"@stylistic/eslint-plugin": "^2.3.0", "@stylistic/eslint-plugin": "^2.3.0",
"@types/eslint__js": "^8.42.3",
"chai": "^5.1.1",
"chai-as-promised": "^8.0.0",
"cypress": "^13.12.0", "cypress": "^13.12.0",
"eslint": "^8.40.0" "eslint": "^8.57.0",
"http-server": "^14.1.1",
"microbundle": "^0.15.1",
"mocha": "^10.5.2",
"mustache": "^4.2.0",
"typescript": "^5.5.2",
"typescript-eslint": "^7.14.1"
}, },
"dependencies": { "dependencies": {
"@lokesh.dhakar/quantize": "^1.3.0", "@lokesh.dhakar/quantize": "^1.3.0",
"ndarray-pixels": "^4.1.0" "ndarray-pixels": "^4.1.0",
"sharp": "^0.33.4"
} }
} }

View File

@@ -1,108 +0,0 @@
// TODO: Update using this pr https://github.com/lokesh/color-thief/pull/254
const getPixels = require('get-pixels');
const quantize = require('@lokesh.dhakar/quantize');
function createPixelArray(imgData, pixelCount, quality) {
const pixels = imgData;
const pixelArray = [];
for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (typeof a === 'undefined' || a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
return pixelArray;
}
function validateOptions(options) {
let { colorCount, quality } = options;
if (typeof colorCount === 'undefined' || !Number.isInteger(colorCount)) {
colorCount = 10;
} else if (colorCount === 1) {
throw new Error(
'colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()',
);
} else {
colorCount = Math.max(colorCount, 2);
colorCount = Math.min(colorCount, 20);
}
if (
typeof quality === 'undefined' ||
!Number.isInteger(quality) ||
quality < 1
) {
quality = 10;
}
return {
colorCount,
quality,
};
}
function loadImg(img) {
return new Promise((resolve, reject) => {
getPixels(img, function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
function getColor(img, quality) {
return new Promise((resolve, reject) => {
getPalette(img, 5, quality)
.then((palette) => {
resolve(palette[0]);
})
.catch((err) => {
reject(err);
});
});
}
function getPalette(img, colorCount = 10, quality = 10) {
const options = validateOptions({
colorCount,
quality,
});
return new Promise((resolve, reject) => {
loadImg(img)
.then((imgData) => {
const pixelCount = imgData.shape[0] * imgData.shape[1];
const pixelArray = createPixelArray(
imgData.data,
pixelCount,
options.quality,
);
const cmap = quantize(pixelArray, options.colorCount);
const palette = cmap ? cmap.palette() : null;
resolve(palette);
})
.catch((err) => {
reject(err);
});
});
}
module.exports = {
getColor,
getPalette,
};

View File

@@ -0,0 +1,169 @@
/**
* Color Thief Node v3.0.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com
*
* Thanks
* ------
* Nick Rabinowitz - For creating quantize.js.
* John Schulz - For clean up and optimization. @JFSIII
* Nathan Spady - For adding drag and drop support to the demo page.
*
* License
* -------
* Copyright Lokesh Dhakar
* Released under the MIT license
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
*
* @license MIT
*/
// Thanks to this PR for the work of migrating to ndarray-pixels pr https://github.com/lokesh/color-thief/pull/254
import { getPixels } from 'ndarray-pixels';
import quantize from '@lokesh.dhakar/quantize';
import sharp from 'sharp';
import ndarray from 'ndarray';
interface ColorThiefResult {
'r': number;
'g': number;
'b': number;
}
/**
* Create an array of arrays of pixels from an array of pixels
* @param {number[]} pixels An array of pixels
* @param {number} pixelCount The total number of pixels
* @param {number} quality
* @returns {number[][]} Returns an array of arrays of pixel values ([ r, g, b ])
*/
const createPixelArray = ( pixels: number[], pixelCount: number, quality: number ): number[][] => {
const pixelArray: number[][] = [];
for ( let i = 0; i < pixelCount; i = i + quality ) {
const offset = i * 4;
const r = pixels[ offset + 0 ];
const g = pixels[ offset + 1 ];
const b = pixels[ offset + 2 ];
const a = pixels[ offset + 3 ];
// If pixel is mostly opaque and not white
if ( typeof a === 'undefined' || a >= 125 ) {
if ( !( r > 250 && g > 250 && b > 250 ) ) {
pixelArray.push( [ r, g, b ] );
}
}
}
return pixelArray;
};
/**
* Validate Color-Thief options
* @param {{ colorCount: number; quality: number; }} options The options object
* @returns {{ colorCount: number, quality: number }} The same object, but validated
*/
const validateOptions = ( options: { 'colorCount': number; 'quality': number; } ): { 'colorCount': number; 'quality': number; } => {
let colorCount = options.colorCount;
let quality = options.quality;
if ( typeof colorCount === 'undefined' || !Number.isInteger( colorCount ) ) {
colorCount = 10;
} else if ( colorCount === 1 ) {
throw new Error(
'colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()',
);
} else {
colorCount = Math.max( colorCount, 2 );
colorCount = Math.min( colorCount, 20 );
}
if (
typeof quality === 'undefined' ||
!Number.isInteger( quality ) ||
quality < 1
) {
quality = 10;
}
return {
colorCount,
quality,
};
};
/**
* Load an image from the disk an pre-process
* @param {string} img Path to the image on the disk
* @returns {Promise<ndarray.NdArray<Uint8Array>>} Returns the pre-processed image
*/
const loadImg = ( img: string ): Promise<ndarray.NdArray<Uint8Array>> => {
return new Promise( ( resolve, reject ) => {
sharp( img )
.toBuffer()
.then( ( buffer ) => sharp( buffer ).metadata()
.then( ( metadata ) => ( { buffer, 'format': metadata.format } ) ) )
.then( ( { buffer, format } ) => getPixels( buffer, format ) )
.then( resolve )
.catch( reject );
} );
};
/**
* Get the dominant color of an image
* @param {string} img Path to the image on the disk
* @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {Promise<ColorThiefResult>} Returns the dominant color
*/
const getColor = ( img: string, quality: number = 10 ): Promise<ColorThiefResult> => {
return new Promise( ( resolve, reject ) => {
getPalette( img, 5, quality )
.then( ( palette ) => {
resolve( palette[0] );
} )
.catch( ( err ) => {
reject( err );
} );
} );
};
/**
* Get the color palette of an image
* @param {string} img Path to the image on the disk
* @param {number?} colorCount (Optional) the target amount of colors to try and extract
* @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {Promise<ColorThiefResult[]>} Returns an array of colors
*/
const getPalette = ( img: string, colorCount: number = 10, quality: number = 10 ): Promise<ColorThiefResult[]> => {
const options = validateOptions( {
colorCount,
quality,
} );
return new Promise( ( resolve, reject ) => {
loadImg( img )
.then( ( imgData ) => {
const pixelCount = imgData.shape[0] * imgData.shape[1];
const pixelArray = createPixelArray(
Array.from( imgData.data ),
pixelCount,
options.quality,
);
const cmap = quantize( pixelArray, options.colorCount );
const palette = cmap ? cmap.palette() : null;
resolve( palette );
} )
.catch( ( err ) => {
reject( err );
} );
} );
};
module.exports = {
getColor,
getPalette,
};

View File

@@ -1,147 +0,0 @@
import quantize from '../node_modules/@lokesh.dhakar/quantize/dist/index.mjs';
import core from './core.js';
/*
* Color Thief v2.4.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com
*
* Thanks
* ------
* Nick Rabinowitz - For creating quantize.js.
* John Schulz - For clean up and optimization. @JFSIII
* Nathan Spady - For adding drag and drop support to the demo page.
*
* License
* -------
* Copyright Lokesh Dhakar
* Released under the MIT license
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
*
* @license
*/
/*
CanvasImage Class
Class that wraps the html image element and canvas.
It also simplifies some of the canvas context manipulation
with a set of helper functions.
*/
const CanvasImage = function (image) {
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
this.width = this.canvas.width = image.naturalWidth;
this.height = this.canvas.height = image.naturalHeight;
this.context.drawImage(image, 0, 0, this.width, this.height);
};
CanvasImage.prototype.getImageData = function () {
return this.context.getImageData(0, 0, this.width, this.height);
};
var ColorThief = function () {};
/*
* getColor(sourceImage[, quality])
* returns {r: num, g: num, b: num}
*
* Use the median cut algorithm provided by quantize.js to cluster similar
* colors and return the base color from the largest cluster.
*
* Quality is an optional argument. It needs to be an integer. 1 is the highest quality settings.
* 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
*
* */
ColorThief.prototype.getColor = function (sourceImage, quality = 10) {
const palette = this.getPalette(sourceImage, 5, quality);
const dominantColor = palette[0];
return dominantColor;
};
/*
* getPalette(sourceImage[, colorCount, quality])
* returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...]
*
* Use the median cut algorithm provided by quantize.js to cluster similar colors.
*
* colorCount determines the size of the palette; the number of colors returned. If not set, it
* defaults to 10.
*
* quality is an optional argument. It needs to be an integer. 1 is the highest quality settings.
* 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
* faster the palette generation but the greater the likelihood that colors will be missed.
*
*
*/
ColorThief.prototype.getPalette = function (sourceImage, colorCount, quality) {
const options = core.validateOptions({
colorCount,
quality,
});
// Create custom CanvasImage object
const image = new CanvasImage(sourceImage);
const imageData = image.getImageData();
const pixelCount = image.width * image.height;
const pixelArray = core.createPixelArray(
imageData.data,
pixelCount,
options.quality,
);
// Send array to quantize function which clusters values
// using median cut algorithm
const cmap = quantize(pixelArray, options.colorCount);
const palette = cmap ? cmap.palette() : null;
return palette;
};
ColorThief.prototype.getColorFromUrl = function (imageUrl, callback, quality) {
const sourceImage = document.createElement('img');
sourceImage.addEventListener('load', () => {
const palette = this.getPalette(sourceImage, 5, quality);
const dominantColor = palette[0];
callback(dominantColor, imageUrl);
});
sourceImage.src = imageUrl;
};
ColorThief.prototype.getImageData = function (imageUrl, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', imageUrl, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (this.status == 200) {
let uInt8Array = new Uint8Array(this.response);
i = uInt8Array.length;
let binaryString = new Array(i);
for (let i = 0; i < uInt8Array.length; i++) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
}
let data = binaryString.join('');
let base64 = window.btoa(data);
callback('data:image/png;base64,' + base64);
}
};
xhr.send();
};
ColorThief.prototype.getColorAsync = function (imageUrl, callback, quality) {
const thief = this;
this.getImageData(imageUrl, function (imageData) {
const sourceImage = document.createElement('img');
sourceImage.addEventListener('load', function () {
const palette = thief.getPalette(sourceImage, 5, quality);
const dominantColor = palette[0];
callback(dominantColor, this);
});
sourceImage.src = imageData;
});
};
export default ColorThief;

View File

@@ -1,8 +1,8 @@
import quantize from '../node_modules/@lokesh.dhakar/quantize/dist/index.mjs'; import quantize from '../node_modules/@lokesh.dhakar/quantize/dist/index.mjs';
import core from './core.js'; import core from './core.js';
/* /**
* Color Thief v2.4.0 * Color Thief v3.0.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com * by Lokesh Dhakar - http://www.lokeshdhakar.com
* *
* Thanks * Thanks
@@ -17,50 +17,61 @@ import core from './core.js';
* Released under the MIT license * Released under the MIT license
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE * https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
* *
* @license * @license MIT
*/ */
/* /**
CanvasImage Class @class CanvasImage that wraps the html image element and canvas.
Class that wraps the html image element and canvas.
It also simplifies some of the canvas context manipulation It also simplifies some of the canvas context manipulation
with a set of helper functions. with a set of helper functions.
*/ */
class CanvasImage {
canvas: HTMLCanvasElement;
const CanvasImage = function (image: HTMLImageElement) { context: CanvasRenderingContext2D;
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d'); width: number;
height: number;
constructor ( image: HTMLImageElement ) {
this.canvas = document.createElement( 'canvas' );
this.context = this.canvas.getContext( '2d' );
this.width = this.canvas.width = image.naturalWidth; this.width = this.canvas.width = image.naturalWidth;
this.height = this.canvas.height = image.naturalHeight; this.height = this.canvas.height = image.naturalHeight;
this.context.drawImage(image, 0, 0, this.width, this.height); this.context.drawImage( image, 0, 0, this.width, this.height );
}; }
CanvasImage.prototype.getImageData = function () { getImageData = function () {
return this.context.getImageData(0, 0, this.width, this.height); return this.context.getImageData( 0, 0, this.width, this.height );
}; };
interface ColorThiefResult {
r: number;
g: number;
b: number;
} }
interface ColorThiefResult {
'r': number;
'g': number;
'b': number;
}
/**
* @class The main ColorThief class
*/
class ColorThief { class ColorThief {
/** /**
* Use the median cut algorithm provided by quantize.js to cluster similar * Use the median cut algorithm provided by quantize.js to cluster similar
* colors and return the base color from the largest cluster. * colors and return the base color from the largest cluster.
* @param {HTMLImageElement} sourceImage:HTMLImageElement * @param {HTMLImageElement} sourceImage:HTMLImageElement
* @param {number} quality Quality is an optional argument. It needs to be an integer. 1 is the highest quality settings. * @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* 10 is the default. There is a trade-off between quality and speed. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually * faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color. * most dominant color.
* @returns {ColorThiefResult} returns {r: num, g: num, b: num} * @returns {ColorThiefResult} returns {r: num, g: num, b: num}
*/ */
getColor( getColor (
sourceImage: HTMLImageElement, sourceImage: HTMLImageElement,
quality: number = 10, quality: number = 10,
): ColorThiefResult { ): ColorThiefResult {
const palette = this.getPalette(sourceImage, 5, quality); const palette = this.getPalette( sourceImage, 5, quality );
return palette[0]; return palette[0];
} }
@@ -69,23 +80,23 @@ class ColorThief {
* @param {HTMLImageElement} sourceImage The image you want to have processed, as an HTMLImageElement * @param {HTMLImageElement} sourceImage The image you want to have processed, as an HTMLImageElement
* @param {number?} colorCount colorCount determines the size of the palette; the number of colors returned. If not set, it * @param {number?} colorCount colorCount determines the size of the palette; the number of colors returned. If not set, it
* defaults to 10. * defaults to 10.
* @param {number?} quality quality is an optional argument. It needs to be an integer. 1 is the highest quality settings. * @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* 10 is the default. There is a trade-off between quality and speed. The bigger the number, the * faster a color will be returned but the greater the likelihood that it will not be the visually
* faster the palette generation but the greater the likelihood that colors will be missed. * most dominant color.
* @returns {ColorThiefResult[]} returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...] * @returns {ColorThiefResult[] | null} returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...]
*/ */
getPalette( getPalette (
sourceImage: HTMLImageElement, sourceImage: HTMLImageElement,
colorCount?: number, colorCount?: number,
quality: number = 10, quality: number = 10,
): ColorThiefResult[] { ): ColorThiefResult[] | null {
const options = core.validateOptions({ const options = core.validateOptions( {
colorCount, colorCount,
quality, quality,
}); } );
// Create custom CanvasImage object // Create custom CanvasImage object
const image = new CanvasImage(sourceImage); const image = new CanvasImage( sourceImage );
const imageData = image.getImageData(); const imageData = image.getImageData();
const pixelCount = image.width * image.height; const pixelCount = image.width * image.height;
@@ -97,83 +108,127 @@ class ColorThief {
// Send array to quantize function which clusters values // Send array to quantize function which clusters values
// using median cut algorithm // using median cut algorithm
const cmap = quantize(pixelArray, options.colorCount); const cmap = quantize( pixelArray, options.colorCount );
const palette = cmap ? cmap.palette() : null; const palette: null | ColorThiefResult[] = cmap ? cmap.palette() : null;
return palette; return palette;
} }
/** /**
* Description * [ DEPRECATED ] Get the dominant color of an image, which is fetched from a URL.
* @param {any} imageUrl * @param {string} imageUrl
* @param {any} callback * @param {( color: ColorThiefResult, url: string ) => void} callback The callback function called when the image has finished processing
* @param {any} quality * @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* @returns {any} * faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {void}
* @deprecated since Version 3.0, use getColorFromURLPromise instead
*/ */
getColorFromUrl(imageUrl, callback, quality) { getColorFromUrl ( imageUrl: string, callback: ( color: ColorThiefResult, url: string ) => void, quality: number = 10 ): void {
const sourceImage = document.createElement('img'); const sourceImage = document.createElement( 'img' );
sourceImage.addEventListener('load', () => { sourceImage.addEventListener( 'load', () => {
const palette = this.getPalette(sourceImage, 5, quality); const palette = this.getPalette( sourceImage, 5, quality );
const dominantColor = palette[0]; const dominantColor = palette[0];
callback(dominantColor, imageUrl); callback( dominantColor, imageUrl );
}); } );
sourceImage.src = imageUrl; sourceImage.src = imageUrl;
} }
/** /**
* Description * [ DEPRECATED ] Get Image Data as a Base64 string from a URL
* @param {string} imageUrl * @param {string} imageUrl The URL to the image. Has to be a full URL, if not relative
* @param {string} callback * @param {( data: string ) => void} callback The callback function called, once download is complete. Will only be called if successful at downloading
* @returns {void} * @returns {void}
* @deprecated since Version 3.0, use getImageDataFromURL instead
*/ */
getImageData(imageUrl: string, callback) { getImageData ( imageUrl: string, callback: ( data: string ) => void ): void {
let xhr = new XMLHttpRequest(); fetch( imageUrl ).then( ( res ) => {
xhr.open('GET', imageUrl, true); if ( res.status === 200 ) {
xhr.responseType = 'arraybuffer'; res.arrayBuffer().then( ( response ) => {
const uInt8Array = new Uint8Array( response );
fetch(imageUrl).then((res) => { const binaryString = new Array( uInt8Array.length );
if (res.status === 200) { for ( let i = 0; i < uInt8Array.length; i++ ) {
res.arrayBuffer().then((response) => { binaryString[i] = String.fromCharCode( uInt8Array[i] );
const uInt8Array = new Uint8Array(response);
let binaryString = new Array(uInt8Array.length);
for (let i = 0; i < uInt8Array.length; i++) {
binaryString[i] = String.fromCharCode(uInt8Array[i]);
} }
let data = binaryString.join(''); const data = binaryString.join( '' );
let base64 = window.btoa(data); callback( 'data:image/png;base64,' + window.btoa( data ) );
}); } );
} }
}); } );
xhr.onload = function () {
if (this.status == 200) {
let uInt8Array = new Uint8Array(this.response);
callback('data:image/png;base64,' + base64);
}
};
xhr.send();
} }
/** /**
* Description * Get Image Data as a Base64 string from a URL
* @param {any} imageUrl * @param {string} imageUrl The URL to the image. Has to be a full URL, if not relative
* @param {any} callback * @returns {Promise<string>} returns a Promise resolving to a string with the base64 string
* @param {any} quality
* @returns {any}
*/ */
getColorAsync = function (imageUrl, callback, quality) { getImageDataFromURL ( imageUrl: string ): Promise<string> {
return new Promise( ( resolve, reject ) => {
fetch( imageUrl ).then( ( res ) => {
if ( res.status === 200 ) {
res.arrayBuffer().then( ( response ) => {
const uInt8Array = new Uint8Array( response );
const binaryString = new Array( uInt8Array.length );
for ( let i = 0; i < uInt8Array.length; i++ ) {
binaryString[i] = String.fromCharCode( uInt8Array[i] );
}
const data = binaryString.join( '' );
resolve( 'data:image/png;base64,' + window.btoa( data ) );
} );
} else {
reject( 'ERR_LOADING' );
}
} );
} );
}
/**
* [ DEPRECATED ] Same as getColor, but async
* @param {string} imageUrl
* @param {( data: ColorThiefResult ) => void} callback
* @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {void}
* @deprecated since Version 3.0, in favour of getColorPromise. Only retained for compatibility
*/
getColorAsync = ( imageUrl: string, callback: ( data: ColorThiefResult, img: HTMLImageElement ) => void, quality: number | null ): void => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const thief = this; const thief = this;
this.getImageData(imageUrl, function (imageData) { this.getImageDataFromURL( imageUrl ).then( ( imageData ) => {
const sourceImage = document.createElement('img'); const sourceImage = document.createElement( 'img' );
sourceImage.addEventListener('load', function () { sourceImage.addEventListener( 'load', () => {
const palette = thief.getPalette(sourceImage, 5, quality); const palette = thief.getPalette( sourceImage, 5, quality );
const dominantColor = palette[0]; callback( palette[ 0 ], sourceImage );
callback(dominantColor, this); } );
});
sourceImage.src = imageData; sourceImage.src = imageData;
}); } );
};
/**
* Same as getColor, but promise-based
* @param {string} imageUrl
* @param {number?} quality (Optional) 1 = highest quality, 10 = default. The bigger the number, the
* faster a color will be returned but the greater the likelihood that it will not be the visually
* most dominant color.
* @returns {Promise<{ 'color': ColorThiefResult, 'img': HTMLImageElement }>} Returns a promise resolving to an object containing the color and the image element
*/
getColorPromise = ( imageUrl: string, quality: number | null ): Promise<{ 'color': ColorThiefResult, 'img': HTMLImageElement }> => {
return new Promise( ( resolve, reject ) => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const thief = this;
this.getImageDataFromURL( imageUrl ).then( ( imageData ) => {
const sourceImage = document.createElement( 'img' );
sourceImage.addEventListener( 'load', () => {
const palette = thief.getPalette( sourceImage, 5, quality );
resolve( { 'color': palette[ 0 ], 'img': sourceImage } );
} );
sourceImage.src = imageData;
} ).catch( ( e ) => {
reject( e );
} );
} );
}; };
} }

View File

@@ -1,53 +0,0 @@
function createPixelArray(imgData, pixelCount, quality) {
const pixels = imgData;
const pixelArray = [];
for (let i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (typeof a === 'undefined' || a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
return pixelArray;
}
function validateOptions(options) {
let { colorCount, quality } = options;
if (typeof colorCount === 'undefined' || !Number.isInteger(colorCount)) {
colorCount = 10;
} else if (colorCount === 1) {
throw new Error(
'colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()',
);
} else {
colorCount = Math.max(colorCount, 2);
colorCount = Math.min(colorCount, 20);
}
if (
typeof quality === 'undefined' ||
!Number.isInteger(quality) ||
quality < 1
) {
quality = 10;
}
return {
colorCount,
quality,
};
}
export default {
createPixelArray,
validateOptions,
};

View File

@@ -0,0 +1,84 @@
/**
* Color Thief Core v3.0.0
* by Lokesh Dhakar - http://www.lokeshdhakar.com
*
* Thanks
* ------
* Nick Rabinowitz - For creating quantize.js.
* John Schulz - For clean up and optimization. @JFSIII
* Nathan Spady - For adding drag and drop support to the demo page.
*
* License
* -------
* Copyright Lokesh Dhakar
* Released under the MIT license
* https://raw.githubusercontent.com/lokesh/color-thief/master/LICENSE
*
* @license MIT
*/
/**
* Create an array of arrays of pixels from an array of pixels
* @param {number[]} pixels An array of pixels
* @param {number} pixelCount The total number of pixels
* @param {number} quality
* @returns {number[][]} Returns an array of arrays of pixel values ([ r, g, b ])
*/
const createPixelArray = ( pixels: number[], pixelCount: number, quality: number ): number[][] => {
const pixelArray: number[][] = [];
for ( let i = 0; i < pixelCount; i = i + quality ) {
const offset = i * 4;
const r = pixels[ offset + 0 ];
const g = pixels[ offset + 1 ];
const b = pixels[ offset + 2 ];
const a = pixels[ offset + 3 ];
// If pixel is mostly opaque and not white
if ( typeof a === 'undefined' || a >= 125 ) {
if ( !( r > 250 && g > 250 && b > 250 ) ) {
pixelArray.push( [ r, g, b ] );
}
}
}
return pixelArray;
};
/**
* Validate Color-Thief options
* @param {{ colorCount: number; quality: number; }} options The options object
* @returns {{ colorCount: number, quality: number }} The same object, but validated
*/
const validateOptions = ( options: { 'colorCount': number; 'quality': number; } ): { 'colorCount': number; 'quality': number; } => {
let colorCount = options.colorCount;
let quality = options.quality;
if ( typeof colorCount === 'undefined' || !Number.isInteger( colorCount ) ) {
colorCount = 10;
} else if ( colorCount === 1 ) {
throw new Error(
'colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()',
);
} else {
colorCount = Math.max( colorCount, 2 );
colorCount = Math.min( colorCount, 20 );
}
if (
typeof quality === 'undefined' ||
!Number.isInteger( quality ) ||
quality < 1
) {
quality = 10;
}
return {
colorCount,
quality,
};
};
export default {
createPixelArray,
validateOptions,
};

View File

@@ -1,24 +1,24 @@
const { resolve } = require('path'); import { resolve } from 'path';
const ColorThief = require(resolve(process.cwd(), "dist/color-thief.js")); import ColorThief from '../dist/color-thief-node.cjs';
const img = resolve(process.cwd(), 'cypress/test-pages/img/rainbow-vertical.png'); const img = resolve( process.cwd(), 'cypress/test-pages/img/rainbow-vertical.png' );
const chai = require("chai"); import { expect as _expect, use } from 'chai';
const expect = chai.expect; const expect = _expect;
chai.use(require("chai-as-promised")); import chaiAsPromised from 'chai-as-promised';
use( chaiAsPromised );
describe( 'getColor()', function () {
it( 'returns valid color', function () {
return expect( ColorThief.getColor( img ) ).to.eventually.have.lengthOf( 3 );
} );
} );
describe('getColor()', function() { describe( 'getPalette()', function () {
it('returns valid color', function() { it( 'returns 5 colors when colorCount set to 5', function () {
return expect(ColorThief.getColor(img)).to.eventually.have.lengthOf(3); return expect( ColorThief.getPalette( img, 5 ) ).to.eventually.have.lengthOf( 5 );
}); } );
});
describe('getPalette()', function() { it( 'returns 9 colors when colorCount set to 9', function () {
it('returns 5 colors when colorCount set to 5', function() { return expect( ColorThief.getPalette( img, 9 ) ).to.eventually.have.lengthOf( 9 );
return expect(ColorThief.getPalette(img, 5)).to.eventually.have.lengthOf(5); } );
}); } );
it('returns 9 colors when colorCount set to 9', function() {
return expect(ColorThief.getPalette(img, 9)).to.eventually.have.lengthOf(9);
});
});

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"moduleResolution": "NodeNext",
"module": "NodeNext"
},
"include": [ "./src/**/*" ],
}