lovell / sharp

High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library.
https://sharp.pixelplumbing.com
Apache License 2.0
28.96k stars 1.29k forks source link

Calculate dominant colour #640

Closed refo closed 4 years ago

refo commented 7 years ago

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

lovell commented 7 years ago

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

rn4391 commented 7 years ago

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.

Shuunen commented 7 years ago

Would be great to have this feature :+1:

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

ile commented 7 years ago

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

homerjam commented 7 years ago

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?

lovell commented 7 years ago

@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.

coffeebite commented 6 years ago

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

medanat commented 6 years ago

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
  });
prewk commented 6 years ago

@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!

medanat commented 6 years ago

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.

jacktuck commented 6 years ago

Any update? <3

jacktuck commented 6 years ago

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)
          ]
        })
    })
}
MikeRock commented 5 years ago

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'))
      })
    })
prewk commented 5 years ago

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.)

kapouer commented 4 years ago

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

polarathene commented 4 years ago

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.

lovell commented 4 years ago

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.

fgilio commented 4 years ago

Thanks!

lovell commented 4 years ago

v0.26.0 now available.

PlopTheReal commented 4 years ago

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

nunowar commented 2 years ago

Is possible to extract the top 5 dominant colors from the image with Sharp? Using the stats.dominant I just get one color. I need to get the Palette as https://jariz.github.io/vibrant.js/ does. This is possible with Sharp?

prewk commented 2 years ago

Is possible to extract the top 5 dominant colors from the image with Sharp? Using the stats.dominant I just get one color. I need to get the Palette as https://jariz.github.io/vibrant.js/ does. This is possible with Sharp?

If you read the thread you'll find the answers

Aminelahlou commented 1 year ago

Hello everyone, I am using sharp for printing textiles and am also intersted in making palette. I have played with some algorithms for making palettes. One of the algorithms I found very interesting is the octree color quantization: https://observablehq.com/@tmcw/octree-color-quantization. I think it would perfectly fit the problem detailed by the author (except I managed to make only 4 dominant color. the algorithm is a bit complex to change for me to make it supports 20 differents colors.

With this algorithm, do you think it could be possible to modulate with brigthness, saturation, and hue each pixel with different parameters depending on the distance of pixel that need to be changed with the color palette?

Exemple: I have image I with 700 000 pixels. I found 3 main colors [{r:255,g:100,b:50},{r:0,g:30,b:50},{r:70,g:90,b:150}].

I then calculate the squared distance of the original pixel {{r:30,g:55,b:90}with each palette color: distances=[(255-30)^2+(100-55)^2+(50-90)^2,(0-30)^2+(30-55)^2+(50-90)^2,(70-30)^2+(90-55)^2+(150-90)^2]which equals to [54250,3125,6425] which means the second color from the palette is near the original pixel:

I then apply a modulate function depending on specific parameters [{hue: 50}, {hue:90,saturation:1.05},{hue:40}] since the second pixel from the palette is nearest I apply those parameters {hue:90,saturation:1.05} onto the pixel.

Does sharp supports this types of operations?