diff --git a/build/build.mjs b/build/build.mjs index 276899c..4e550cb 100644 --- a/build/build.mjs +++ b/build/build.mjs @@ -17,9 +17,10 @@ duplicating the UMD compatible file and giving it that name. const umdRelPath = 'dist/color-thief.umd.js'; const legacyRelPath = 'dist/color-thief.min.js'; +const workingDir = path.resolve( path.dirname( '' ) ); -const umdPath = path.join( path.resolve( path.dirname( '' ) ), umdRelPath ); -const legacyPath = path.join( path.resolve( path.dirname( '' ) ), legacyRelPath ); +const umdPath = path.join( workingDir, umdRelPath ); +const legacyPath = path.join( workingDir, legacyRelPath ); copyFile( umdPath, legacyPath, ( err ) => { if ( err ) throw err; @@ -27,11 +28,23 @@ copyFile( umdPath, legacyPath, ( err ) => { } ); const srcNodeRelPath = 'built/color-thief-node.js'; -const distNodeRelPath = 'dist/color-thief.js'; -const srcNodePath = path.join( path.resolve( path.dirname( '' ) ), srcNodeRelPath ); -const distNodePath = path.join( path.resolve( path.dirname( '' ) ), distNodeRelPath ); +const distNodeRelPath = 'dist/color-thief-node.js'; +const srcNodePath = path.join( workingDir, srcNodeRelPath ); +const distNodePath = path.join( workingDir, distNodeRelPath ); copyFile( srcNodePath, distNodePath, ( err ) => { if ( err ) throw err; console.log( `${ srcNodeRelPath } copied to ${ distNodeRelPath }.` ); } ); + + +// Copy the .d.ts files to dist as well +copyFile( path.join( workingDir, 'built/color-thief-node.d.ts' ), path.join( workingDir, 'dist/color-thief-node.d.ts' ), ( err ) => { + if ( err ) throw err; + console.log( 'Definitions copied (node)' ); +} ); + +copyFile( path.join( workingDir, 'built/color-thief.d.ts' ), path.join( workingDir, 'dist/color-thief.d.ts' ), ( err ) => { + if ( err ) throw err; + console.log( 'Definitions copied (node)' ); +} ); diff --git a/dist/color-thief-node.d.ts b/dist/color-thief-node.d.ts new file mode 100644 index 0000000..ba0f4cd --- /dev/null +++ b/dist/color-thief-node.d.ts @@ -0,0 +1,19 @@ +/** + * 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 + */ +export {}; diff --git a/dist/color-thief-node.js b/dist/color-thief-node.js new file mode 100644 index 0000000..8e2be2a --- /dev/null +++ b/dist/color-thief-node.js @@ -0,0 +1,141 @@ +/** + * 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'; +/** + * 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 = []; + 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) => { + 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>} Returns the pre-processed image + */ +const loadImg = (img) => { + 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} Returns the dominant color + */ +const getColor = (img, quality = 10) => { + 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} Returns an array of colors + */ +const 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(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, +}; diff --git a/dist/color-thief.d.ts b/dist/color-thief.d.ts new file mode 100644 index 0000000..a650c25 --- /dev/null +++ b/dist/color-thief.d.ts @@ -0,0 +1,80 @@ +interface ColorThiefResult { + 'r': number; + 'g': number; + 'b': number; +} +/** + * @class The main ColorThief class + */ +declare class ColorThief { + /** + * Use the median cut algorithm provided by quantize.js to cluster similar + * colors and return the base color from the largest cluster. + * @param {HTMLImageElement} sourceImage:HTMLImageElement + * @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 {ColorThiefResult} returns {r: num, g: num, b: num} + */ + getColor(sourceImage: HTMLImageElement, quality?: number): ColorThiefResult; + /** + * Use the median cut algorithm provided by quantize.js to cluster similar colors. + * @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 + * defaults to 10. + * @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 {ColorThiefResult[] | null} returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...] + */ + getPalette(sourceImage: HTMLImageElement, colorCount?: number, quality?: number): ColorThiefResult[] | null; + /** + * [ DEPRECATED ] Get the dominant color of an image, which is fetched from a URL. + * @param {string} imageUrl + * @param {( color: ColorThiefResult, url: string ) => void} callback The callback function called when the image has finished processing + * @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, use getColorFromURLPromise instead + */ + getColorFromUrl(imageUrl: string, callback: (color: ColorThiefResult, url: string) => void, quality?: number): void; + /** + * [ DEPRECATED ] Get Image Data as a Base64 string from a URL + * @param {string} imageUrl The URL to the image. Has to be a full URL, if not relative + * @param {( data: string ) => void} callback The callback function called, once download is complete. Will only be called if successful at downloading + * @returns {void} + * @deprecated since Version 3.0, use getImageDataFromURL instead + */ + getImageData(imageUrl: string, callback: (data: string) => void): void; + /** + * Get Image Data as a Base64 string from a URL + * @param {string} imageUrl The URL to the image. Has to be a full URL, if not relative + * @returns {Promise} returns a Promise resolving to a string with the base64 string + */ + getImageDataFromURL(imageUrl: string): Promise; + /** + * [ 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; + /** + * 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; + }>; +} +export default ColorThief;