🎨 Extract prominent colors from an image

Last update: Aug 4, 2022

node-vibrant

Build Status

Extract prominent colors from an image.

** NOTICE **

This project is refactored into a monorepo in version 3.2.0 (see develop branch, npm version [email protected]).

We will not merge new PRs to v3.1 related to new featuresets during this time. However, bug fixes and security vulnerability fixes are still highly encouraged.

New WebWorker support in v3.0

Quantization is the most time-consuming stage in node-vibrant. In v3.0, the quantization can be run in the WebWorker to avoid freezing the UI thread.

Here's how to use this feature:

  1. Use WebWorker build dist/vibrant.worker.js or dist/vibrant.worker.min.js. Or if you are re-bundling with webpack, use lib/bundle.worker.js as entry
  2. Use WebWorker quantizer:
    let v = Vibrant.from(src)
      .useQuantizer(Vibrant.Quantizer.WebWorker)
      // Other configurations

Features

  • Identical API for both node.js and browser environment
  • Support browserify/webpack
  • Consistent results (*See Result Consistency)

Install

$ npm install node-vibrant

Usage

node.js / browserify

// ES5
var Vibrant = require('node-vibrant')
// ES6
import * as Vibrant from 'node-vibrant'
// TypeScript
import Vibrant = require('node-vibrant')

// Using builder
Vibrant.from('path/to/image').getPalette((err, palette) => console.log(palette))
// Promise
Vibrant.from('path/to/image').getPalette()
  .then((palette) => console.log(palette))

// Using constructor
let v = new Vibrant('path/to/image', opts)
v.getPalette((err, palette) => console.log(palette))
// Promise
v.getPalette().then((palette) => console.log(palette))

Browser

If you installed node-vibrant with npm, compiled bundles are available under node_modules/node-vibrant/dist. Or you can download bundles from Relases.

<!-- Debug version -->
<script src="/path/to/dist/vibrant.js"></script>
<!-- Uglified version -->
<script src="/path/to/dist/vibrant.min.js"></script>

<script>
  // Use `Vibrant` in script
  // Vibrant is exported to global. window.Vibrant === Vibrant
  Vibrant.from('path/to/image').getPalette(function(err, palette) {});
  // Promise
  Vibrant.from('path/to/image').getPalette().then(function(palette) {});
  // Or
  var v = new Vibrant('/path/to/image', opts);
  // ... same as in node.js
</script>

Contribution Guidelines

  1. Make changes
  2. Write test specs if necessary
  3. Pass tests
  4. Commit source files only (without compiled files)

References

Vibrant

Main class of node-vibrant.

Vibrant.from(src: ImageSource): Builder

Make a Builder for an image. Returns a Builder instance.

constructor(src: ImageSource, opts: Partial<Options>)

Name Description
image Path to image file (support HTTP/HTTPs)
opts Options (optional)
ImageSource
export type ImageSource = string
  | HTMLImageElement  // Browser only
  | Buffer            // Node.js only
Options
export interface Options {
    colorCount: number
    quality: number
    maxDimension: number
    filters: Array<Filter>
    ImageClass: ImageClass
    quantizer: Quantizer
    generator?: Generator
}
Field Default Description
colorCount 64 amount of colors in initial palette from which the swatches will be generated
quality 5 Scale down factor used in downsampling stage. 1 means no downsampling. If maxDimension is set, this value will not be used.
maxDimension undefined The max size of the image's longer side used in downsampling stage. This field will override quality.
filters [] An array of filters
ImageClass Image.Node or Image.Browser An Image implementation class
quantizer Vibrant.Quantizer.MMCQ A Quantizer implementation class
generator Vibrant.Generator.Default An Generator instance
Resolvable<T>
export type Resolvable<T> = T | Promise<T>
Quantizer
export interface Quantizer {
    (pixels: Pixels, opts: Options): Resolvable<Array<Swatch>>
}
Generator
export interface Generator {
    (swatches: Array<Swatch>, opts?: Object): Resolvable<Palette>
}
Filter

Returns true if the color is to be kept.

export interface Filter {
    (red: number, green: number, blue: number, alpha: number): boolean
}

getPalette(cb?: Callback<Palette>): Promise<Palette>

Name Description
cb (Optional) callback function. Can be omitted when using Promise.
Callback<T>
export interface Callback<T> {
    (err?: Error, result?: T): void
}

getSwatches(cb?: Callback<Palette>): Promise<Palette>

Alias of getPalette.

Vibrant.Builder

Helper class for change configurations and create a Vibrant instance. Methods of a Builder instance can be chained like:

Vibrant.from(src)
  .quality(1)
  .clearFilters()
  // ...
  .getPalette()
  .then((palette) => {})

constructor(src: ImageSource, opts: Partial<Options>)

Arguments are the same as Vibrant.constructor.

quality(q: number): Builder

Sets opts.quality to q. Returns this Builder instance.

maxColorCount(n: number): Builder

Sets opts.colorCount to n. Returns this Builder instance.

maxDimension(d: number): Builder

Sets opts.maxDimension to d. Returns this Builder instance.

addFilter(f: Filter): Builder

Adds a filter function. Returns this Builder instance.

removeFilter(f: Filter): Builder

Removes a filter function. Returns this Builder instance.

clearFilters(): Builder

Clear all filters. Returns this Builder instance.

useImageClass(imageClass: ImageClass): Builder

Specifies which Image implementation class to use. Returns this Builder instance.

useQuantizer(quantizer: Quantizer): Builder

Specifies which Quantizer implementation class to use. Returns this Builder instance.

useGenerator(generator: Generator): Builder

Sets opts.generator to generator. Returns this Builder instance.

build(): Vibrant

Builds and returns a Vibrant instance as configured.

getPalette(cb?: Callback<Palette>): Promise<Palette>

Builds a Vibrant instance as configured and calls its getPalette method.

getSwatches(cb? Callback<Palette>): Promise<Palette>

Alias of getPalette.

Vibrant.Swatch

Represents a color swatch generated from an image's palette.

Vec3

export interface Vec3 extends Array<number> {
    0: number,
    1: number,
    2: number
}

constructor(rgb: Vec3, population: number)

Internal use.

Name Description
rgb [r, g, b]
population Population of the color in an image

getHsl(): Vec3

getPopulation(): number

getRgb(): Vec3

getHex(): string

getTitleTextColor(): string

Returns an appropriate color to use for any 'title' text which is displayed over this Swatch's color.

getBodyTextColor(): string

Returns an appropriate color to use for any 'body' text which is displayed over this Swatch's color.

Vibrant.Util

Utility methods. Internal usage.

hexToRgb(hex: string): Vec3

rgbToHex(r: number, g: number, b: number): string

hslToRgb(h: number, s: number, l: number): Vec3

rgbToHsl(r: number, g: number, b: number): Vec3

xyzToRgb(x: number, y: number, z: number): Vec3

rgbToXyz(r: number, g: number, b: number): Vec3

xyzToCIELab(x: number, y: number, z: number): Vec3

rgbToCIELab(l: number, a: number, b: number): Vec3

deltaE94(lab1: number, lab2: number): number

Computes CIE delta E 1994 diff between lab1 and lab2. The 2 colors are in CIE-Lab color space. Used in tests to compare 2 colors' perceptual similarity.

rgbDiff(rgb1: Vec3, rgb2: Vec3): number

Compute CIE delta E 1994 diff between rgb1 and rgb2.

hexDiff(hex1: string, hex2: string): number

Compute CIE delta E 1994 diff between hex1 and hex2.

getColorDiffStatus(d: number): string

Gets a string to describe the meaning of the color diff. Used in tests.

Delta E Perception Returns
<= 1.0 Not perceptible by human eyes. "Perfect"
1 - 2 Perceptible through close observation. "Close"
2 - 10 Perceptible at a glance. "Good"
11 - 49 Colors are more similar than opposite "Similar"
50 - 100 Colors are exact opposite Wrong

NPM Tasks

Task Description
build:browser Build browser target
build:node Build node.js target
build Build all targets
clean:browser Clean browser build
clean:node Clean node.js build
clean Clean all builds
test:browser Run browser specs (karma)
test:node Run node.js specs (mocha)
test Run all specs

Notes

Intentional Deviation From vibrant.js

  • node-vibrant takes image path, not the image object as parameter for the obvious reason that node.js environment has no access to HTML DOM object.
  • node-vibrant provides asynchronous API since most node.js image processing library is asynchronous. And the original vibrant.js workflow is asynchronous any way (though you will have to handle the image loading yourself, while node-vibrant does it for you).
  • node-vibrant uses one single opts object to hold all options for future expansions. And it feels more node.js-like.
  • node-vibrant uses method call to initiate image processing instead of constructor so that developers can use it with Promise.

Result Consistency

The results is consistent within each user's browser instance regardelss of visible region or display size of the image, unlike the original vibrant.js implementation.

However, due to the very nature of HTML5 canvas element, image rendering is platform/machine-dependent. Thus the resulting swatches in browser environment varies and may not be the same as in node.js nor in another machine. See Canvas Fingerprinting.

The test specs use CIE delta E 1994 color difference to measure inconsistencies across platforms. It compares the generated color on node.js, Chrome, Firefox and IE11. At quality == 1 (no downsampling) and no filters, the results are rather consistent. Color diffs between browsers are mostly not perceptible by human eyes. Downsampling will cause perceptible inconsistent results across browsers due to differences in canvas implementations.

GitHub

https://github.com/Vibrant-Colors/node-vibrant
Comments
  • 1. Fill more empty swatches (update and expansion on #51)

    Muted variations were not being filled if empty, this adds them so they're available from Vibrant. Also, try filling Vibrant from LightVibrant (if available). This fixes ismamz/postcss-get-color#3

    As mentioned previously in #51.

    This PR was made because there were some non-trivial merge requests (and issues with the previous merge request branch, given that there was a git push -f at some point on master since). I've done my best to preserve original credit in the commit author field, even as I had to rewrite the CoffeeScript to TypeScript :)

    Reviewed by crutchcorn at 2018-10-20 15:15
  • 2. 'parentNode' of undefined Error

    I'm attempting to use this module alongside React. I'm getting the current error:

    Cannot read property 'parentNode' of undefined
        at BroswerImage../node_modules/node-vibrant/lib/image/browser.js.BroswerImage.remove
    
    Reviewed by internette at 2018-08-09 19:35
  • 3. Reduce library size

    I see this library declares jimp as a dependency for doing some transformations.

    I'm analyzing my projects dependencies (I have node-vibrant in my package.json and looks like jimp need a lot of spaces:

    ncdu 1.14 ~ Use the arrow keys to navigate, press ? for help
    --- /deploy/out/node_modules ---------------
      185.0 MiB [##########] /@jimp
       38.4 MiB [##        ] /chrome-aws-lambda
       34.2 MiB [#         ] /sharp
       11.0 MiB [          ] /@browserless
       11.0 MiB [          ] /@babel
        9.2 MiB [          ] /jimp
        6.3 MiB [          ] /core-js
        4.8 MiB [          ] /lodash
        3.2 MiB [          ] /jsdom
        3.1 MiB [          ] /moment
        3.0 MiB [          ] /iltorb
        2.4 MiB [          ] /colorable
        2.1 MiB [          ] /cssstats
        1.9 MiB [          ] /@cliqz
        1.7 MiB [          ] /@microlink
        1.7 MiB [          ] /graphql
        1.7 MiB [          ] /port-numbers
        1.7 MiB [          ] /node-vibrant
    

    The point is, jimp includes some plugin by default:

    --- /deploy/out/node_modules/@jimp ---------
                             /..
        7.6 MiB [##########] /plugin-print
        7.0 MiB [######### ] /core
        6.9 MiB [######### ] /plugin-resize
        6.9 MiB [######### ] /plugin-color
        6.8 MiB [########  ] /plugin-crop
        6.8 MiB [########  ] /plugin-blur
        6.8 MiB [########  ] /plugin-rotate
        6.8 MiB [########  ] /png
        6.8 MiB [########  ] /custom
        6.8 MiB [########  ] /plugin-blit
        6.8 MiB [########  ] /plugin-contain
        6.8 MiB [########  ] /plugin-normalize
        6.8 MiB [########  ] /plugins
        6.8 MiB [########  ] /plugin-cover
        6.8 MiB [########  ] /plugin-gaussian
        6.8 MiB [########  ] /plugin-scale
        6.8 MiB [########  ] /bmp
        6.8 MiB [########  ] /plugin-mask
        6.8 MiB [########  ] /plugin-displace
        6.8 MiB [########  ] /jpeg
    

    but not sure if all the plugins are relevant for node-vibrant.

    I want to suggest two approaches:

    Consider use sharp

    (my favorite solution)

    As you can see in my bundle, I have also sharp as a dependency.

    The main difference between both is jimp is 100% javascript code, while sharp delegate into son binaries.

    At the first time I thought jimp could be better since it doesn't have dependencies, but the reality is so different: sharp ship pre-installed binaries and the package size is actually tooo much smaller than jimp.

    In fact, sharp perf is superior, see http://sharp.pixelplumbing.com/en/stable/performance/

    Just exclude non necessary jimp

    I suppose that inside 185MB that jimp is adding there, just a few things are actually used.

    Not sure how to do that from a node-vibrant, but for example adding a tiny section on README.md explicitly listed the necessary jimp plugins should be enough for anyone exclude the rest of non necessary things into a pre-build step

    Reviewed by Kikobeats at 2019-06-21 11:15
  • 4. Inconsistent values from getPalette()

    I have a huge list of images that I'm running through node-vibrant. I'm noticing quite often the values of the getPalette() object are null, instead of an instance of Swatch.

    Is this a bug or is there some logic behind this? It certainly makes it difficult to programmatically associate some colours with an image.

    Reviewed by daviestar at 2017-03-15 13:30
  • 5. Feature request - image from URL

    Would like to do something like v = new Vibrant('http://slimages.macys.com/is/image/MCY/products/0/optimized/2899260_fpx.tif', opts)

    If this is something you'd like part of your lib, I can send you a PR :)

    Reviewed by pbrandt1 at 2015-08-03 21:06
  • 6. Angular 4+

    Just to make sure anyone trying to use this library in Angular can find an answer, you have to add:

    "allowSyntheticDefaultImports": true,
    

    to the tsconfig.json file at the root of your project for this to work:

    import * as Vibrant from 'node-vibrant'
    

    After that, everything works fine. I'm not sure if something can be done with the typings to make this unnecessary or not.

    Reviewed by jafaircl at 2017-09-07 19:02
  • 7. Restrict output to a predefined array of colours

    I'd like to use Vibrant to help populate a database of products with colour information to be used in a colour search feature. I'd like to restrict the number of colours that people can search on so it doesn't get overwhelming.

    So, for example, if an image has 8 hues of blue in it, Vibrant would only output a single object with the hex value equal to one I've previously selected as the default value for blue.

    Is it possible to do that with Vibrant?

    Reviewed by tyssen at 2015-12-22 06:55
  • 8. Updating opts to enable overriding all algo parameters

    Hi!

    I've recently used this cool package but to get the results I was looking for I had to override some of the algo HSL, quality and weight params. This change enables providig all algo params through the opts constructor argument.

    I also updated readme.md with params documentation.

    Reviewed by lupesko at 2015-09-25 19:23
  • 9. TypeError: CreateListFromArrayLike called on non-object

    Using ReactJS (^16.13.0) and npm. Went through other issues, made sure to do npm install --save-dev worker-loader after npm install [email protected] but error didn't resolve. The only error that resolved was the this.WorkerClass is not a constructor error mentioned in #85 .

    Console logs this line multiple times: worker.ts:16 Uncaught TypeError: CreateListFromArrayLike called on non-object at self.onmessage

    Interestingly, let v = new Vibrant(IMG); console.log(v); logs a Vibrant object with result(...), _src, and opts -- but errors persist.

    Reviewed by diragb at 2020-04-18 16:07
  • 10. Bugfix canvas tainted race condition

    Fixed #92. Avoids race condition by adding anonymous before setting the img.src. This way the crossOrigin will always be set when the image is being loaded.

    Reviewed by timlenaers at 2019-05-15 19:41
  • 11. How to use worker build with Webpack...?

    So i'm having a rough time trying to get the worker flavour of this component working within a webpack environment.

    Basically, when you import as directed in the readme.md - the result is an empty object.

    import Vibrant from 'node-vibrant/dist/vibrant.worker'; 
    console.log(Vibrant) // returns {}
    
    import Vibrant from 'node-vibrant/dist/vibrant.worker.min';
    console.log(Vibrant) // returns {}
    
    // Normal import - works fine... (just doesn't seem to have the worker functionality...)
    import Vibrant from 'node-vibrant';
    console.log(Vibrant) /* returns ƒ Vibrant(_src, opts) {
       this._src = _src;
       this.opts = defaults({}, opts, Vibrant.DefaultOpts);
       this.opts.combinedFilter = Filters.combineFilters(this.opts.filters);
    } */
    

    Have you any ideas on how to fix?

    Reviewed by glomotion at 2018-05-08 01:26
  • 12. Working with extracted HSL values

    Hello!

    This may seem like a really stupid question, but I am only interested in the extracted hue value. But I am not sure how I am supposed to correctly work with the returned value? I am trying to extract the hue value and feed it to a css custom property, which uses it in a hsl() function, which requires the hue to be in degrees, but the value that is being returned is a number from 0 - 1.

    What is the intended way to work with hsl? Is the idea to convert this number to a valid HSL value?

    Reviewed by nachtfunke at 2022-04-11 11:49
  • 13. Different results on desktop vs mobile browser for same image

    Bug

    Mobile browsers and desktop browsers return a different set of colors for the same image.

    Take this image:

    The colors it produces are radically different on a mobile and desktop browser.

    To see this in action, open this URL on both a mobile and desktop browser.

    Steps To Reproduce

    1. Use this image URL: https://res.cloudinary.com/dn29xlaeh/image/upload/q_75,w_640/v1/beatgig-live/quhilykqgarpqnf9peyk
    2. Get the colors on a mobile browser
    3. Get the colors on a desktop browser

    Describe what you expected to happen:

    1. The colors on both platforms should be the same.

    Reproducible sample code

    Here is a minimal CodeSandbox reproduction. I made it as simple as possible.

    These are the colors it comes up with on a desktop browser:

    image

    Meanwhile, here it is on a mobile browser (you can open the sandbox preview URL to view it there.)

    React App

    Reviewed by nandorojo at 2022-04-05 16:18
  • 14. ES6 import is wrong?

    In Node, this doesn't work:

    import * as Vibrant from 'node-vibrant'
    

    I get the error:

    TypeError: Vibrant.from is not a function

    Instead I'm using the regular import which works as expected:

    import Vibrant from 'node-vibrant'
    

    Do you want me to make a PR to fix the docs?

    Reviewed by PierBover at 2021-06-23 21:03
  • 15. Pallete is much different from the source image

    Issue : Palette colors don't match source image Expected : Palette colors should match source image Actual : Palette colors are different from source image

    Steps to reproduce I used this source image to generate a palette. Source Image

    And I got this palette -

    {
      "Vibrant": {
        "rgb": [
          132,
          200,
          68
        ],
        "population": 2
      },
      "DarkVibrant": {
        "rgb": [
          10,
          142,
          70
        ],
        "population": 65
      },
      "LightVibrant": {
        "rgb": [
          166,
          230,
          118
        ],
        "population": 12
      },
      "Muted": {
        "rgb": [
          138,
          168,
          104
        ],
        "population": 67
      },
      "DarkMuted": {
        "rgb": [
          75,
          82,
          50
        ],
        "population": 230
      },
      "LightMuted": {
        "rgb": [
          204,
          212,
          164
        ],
        "population": 1
      }
    }
    

    The palette doesn't change irrespective of quality value or colorCount value. And if you see the color with highest population color ("DarkMuted"), the color is not even part of the image.

    How to tweak this behaviour? I need more accurate palette.

    Reviewed by guloic at 2021-05-26 20:33
  • 16. How to call the util function rgbToHex? Or How to get pallete in Hex format?

    The Readme mentions rgbToHex but the code does not export that function in the util.ts. Any idea how that can be used?

    If getPallete or getSwatches can return hex directly along with population count, that would be great.

    Reviewed by guloic at 2021-05-26 19:34
A Toolbar with Buttons to invert Colors, highlight Links, desaturate and increase Font Size
A Toolbar with Buttons to invert Colors, highlight Links, desaturate and increase Font Size

A Toolbar with Buttons to invert Colors, highlight Links, desaturate and increase Font Size

Aug 15, 2022
Vue component to pick a color from a base64 image, by clicking on it

vue-img-color-picker Vue component to pick a color from a base64 image, by clicking on it. You can access the selected color by $refs.colorPicker.colo

Mar 28, 2021
🎨 Extract prominent colors from an image

node-vibrant Extract prominent colors from an image. ** NOTICE ** This project is refactored into a monorepo in version 3.2.0 (see develop branch, npm

Aug 4, 2022
Webpack loader for extract html in the vue template tag and remove it from bundle js

Vue template extractor Webpack loader for extract html in the vue template tag and remove it from bundle js.

Feb 24, 2020
:art: Help the user picking beautiful colors!
:art: Help the user picking beautiful colors!

Notice Vue Swatches recently reached version 2. If you were using v1 please consider reading the release notes or you can check v1 here Demo And Docum

Aug 11, 2022
Vue directive for breathing colors transition.

Vue Breathing Colorsvue-breathing-colors Vue Breathing Colors is a vue directive used to change background color of any component in a "breathing like

Apr 7, 2022
CSSColorVars (code generation interactive tool), which defines colors with CSS variables, improves the simplicity of the variables and the performance on a website.
CSSColorVars (code generation interactive tool), which defines colors with CSS variables, improves the simplicity of the variables and the performance on a website.

CSSColorVars (code generation interactive tool), which defines colors with CSS variables, improves the simplicity of the variables and the performance

Aug 5, 2021
A Toolbar with Buttons to invert Colors, highlight Links, desaturate and increase Font Size
A Toolbar with Buttons to invert Colors, highlight Links, desaturate and increase Font Size

A Toolbar with Buttons to invert Colors, highlight Links, desaturate and increase Font Size

Aug 15, 2022
A Vue app for generating Magic the Gathering deck by mana colors

Magic deck generator Web app for generating Magic the Gathering deck by mana col

Dec 17, 2021
responsive image with zoomed image on hover
responsive image with zoomed image on hover

vue-zoom-on-hover responsive image with zoomed image on hover. demo (ctrl+click to open in new tab) this vue.js component displays an image with the w

Aug 16, 2022
A Vue component that lets you quickly create responsive image tags with an optimal number of image sources for all devices.

Vue Responsive Image Vue Responsive Image is a Vue component that allows you to quickly insert responsive image tags in your Vue project, provided you

Jul 21, 2022
A Vue component for showing loader during image loading https://john015.github.io/vue-load-image/

English | 한국어 Vue-load-image Vue-load-image is 1KB(gzipped size) minimalist Vue component that display loader during image loading and display alterna

May 24, 2022
A simple image pop-up for keeping image previewing in same ratio

vue-mobile-image-uncover The simplest solution for putting varius ratio images, which you don't want to break your layout. Live Demo (WIP) Usage impor

Jul 29, 2020
Optimus is a desktop image optimization application. It supports conversion and compression between WebP, JPEG, and PNG image formats.
Optimus is a desktop image optimization application. It supports conversion and compression between WebP, JPEG, and PNG image formats.

Optimus is a desktop image optimization application. It supports conversion and compression between WebP, JPEG, and PNG image formats.

Aug 12, 2022
🌈 A Vue image component. Simple realization of image shadow.

?? vue-image-shadow A Vue image component. Simple realization of image shadow. ?? Example Online: https://image-component.github.io/vue-image-shadow/

Mar 31, 2022
vue-image-markup will provide you to edit uploaded image easily and save it.

Markup Image with Vue.js (customizable) vue-image-markup will provide you to edit uploaded image easily and save it. Installation npm i vue-image-mark

Aug 10, 2022
Vue-image-loader: image loader for vue js with prevue

Vue-image-loader: image loader for vue js with prevue

Feb 1, 2020
A Directive for setting fall back image in a vue js application if in case image is not loaded

vue-fall-back-image-directive A Directive for setting fall back image in a vue js application. Installation npm install Run npm install --save vue-fal

Oct 15, 2019
Vue.js lazy load image directive with akamai image converter

vue-lazyload-akamai Vue.js lazy load image directive with akamai image converter ??

Aug 13, 2018
An image picker for image uploading

An image picker for image uploading

Nov 20, 2021