Sharp: Calculate dominant colour

Created on 1 Dec 2016  路  20Comments  路  Source: lovell/sharp

Is it possible to extract prominent colours or single dominant colour using sharp ?

enhancement ready-to-ship

Most helpful comment

Commit c42de19 adds the following API, which uses the 3D histogram technique with 4096 (16^3) RGB bins.

const { dominant } = await sharp(input).stats();
const { r, g, b } = dominant;

This will be in v0.26.0.

All 20 comments

Hello Refik, thanks for raising this topic here. I've been toying with the idea of adding a "stats" feature to expose various calculations based on pixel values and this feature would fit in that category.

This could include any derived data where all the pixel values have to be decompressed so will be much slower than inspecting the uncompressed header data.

We can probably use the histogram binning features of libvips for dominant colours, although I think we'd need to perform quantisation of these values to make them useful.

In the meantime, for those who like to live dangerously, there's the unsupported https://github.com/lovell/attention

This can be done in conjunction with the smartcrop.js module (basically edge detection) to first identify the most important part of the image. This will eliminate cases where a single background colour dominates.

Would be great to have this feature :+1:

Is there any trick to achieve this with current sharp features ?

Yes, and getting the number of colors would be useful too. (For example for distinguishing whether an image is a logo or a photo.)

I like the sound of the stats idea. I still rely on attention for focus region and focus point too (mainly point) - it's amazing, I really haven't found a comparable alternative. It'd be great to see these things ported under utilities perhaps?

I noticed sharp now uses libvips internal cropping methods - does this mean region/point could be exposed somehow?

@homerjam Yes, we could now use vips_smartcrop for a smart extract, where only a width and height are required as input params and the top/left coords of the crop are made available as output parameters. If you're interested, please create a new issue and we can use that to track progress.

Hey @lovell Is there an update on this? How would one go about finding, say, the top 3 colors of an image?

The way I did it with stats:

  sharp(buffer)
    .stats()
    .then(({ channels: [rc, gc, bc] }) => {
      const r = Math.round(rc.mean),
            g = Math.round(gc.mean),
            b = Math.round(bc.mean);

    ... do things with r, g, b
  });

@medanat I'm not sure what kind of result you're after but mean is not a useful measure of prominence/domination. Check out http://jariz.github.io/vibrant.js/ and you'll see what I assume people in this thread are after.

I "solved" the problem by running it through https://github.com/akfish/node-vibrant but I'd love a sharp solution for it!

That was my solution for mimicking the dominant colour functionality of https://github.com/lovell/attention.

In my tests the previous snippet yielded similar or very close results to:

attention('input.jpg')
  .swatches(1)
  .palette(function(err, palette) {
    palette.swatches.forEach(function(swatch) {
      console.dir(swatch);
    });
  });

where the swatch result would give back rgb results. I'm curious to see how else I can approach this.

Any update? <3

A coworker gave me this idea as a workaround, i think it works, just not sure about its efficacy versus, say, using attention library.

Note: pipeline is a sharp instance.

function getDominantColor (pipeline) {
  return pipeline
    .resize(5, 5)
    .crop(sharp.strategy.attention)
    .toBuffer()
    .then(buffer => {
      return sharp(buffer)
        .stats()
        .then(stats => {
          measure()
          console.log('getDominantColor stats', stats)

          const { channels: [r, g, b] } = stats

          return [
            Math.round(r.max),
            Math.round(g.max),
            Math.round(b.max)
          ]
        })
    })
}

Hi. I've been having to the same problem and if you want to get the dominant color returned as an image a simple solution is just setting max blur on it.

const buffer = await new Promise((resolve, reject) => {
      sharp(path.resolve(__dirname,'image.jpg'))
      .resize(3, 3, {
        fit: 'cover',
        position: sharp.strategy.entropy,
      })
      .blur(1000)
      .toBuffer((err, buffer, info) => {
        if (err) reject(err)
        resolve(buffer.toString('base64'))
      })
    })

That doesn't sound very dominant? Sounds more like an avarage value. Check out https://jariz.github.io/vibrant.js/ for a better grasp of what's "dominant" (it's got a very subjective definition.)

This vips issue might be helpful: https://github.com/libvips/libvips/issues/259

color-thief functionality looks to be what I'd be interested in if possible to support.Uses quantization producing a list(~20 elements) of colours ordered by count, with dominant being the most used colour in the image.

In my use-case it's for a placeholder background colour that the source image would have a fade-in transition effect on. Looking at vibrant.js it doesn't seem to an appropriate/reliable type for getting that colour result.

Commit c42de19 adds the following API, which uses the 3D histogram technique with 4096 (16^3) RGB bins.

const { dominant } = await sharp(input).stats();
const { r, g, b } = dominant;

This will be in v0.26.0.

Thanks!

v0.26.0 now available.

Is there is a way to get a palette as I might need to ignore some dominant color (mostly white or black)?

Was this page helpful?
0 / 5 - 0 ratings