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.54k stars 1.28k forks source link

Expose internal colour distance feature (was: Detect whether an image is grayscale) #284

Open rayshan opened 8 years ago

rayshan commented 8 years ago

Hello, would you please consider adding such an operation?

lovell commented 8 years ago

Hi Ray, the metadata function returns a space of b-w for single-channel images. Might this provide what you're looking for?

rayshan commented 8 years ago

I'm planning a workflow like this:

Raw photo -> edit in Lightroom -> export as large jpeg -> resize to smaller versions using sharp for responsive images on the web

Unfortunately Lightroom doesn't seem to support b-w color space or something similar. All my exports are typically in sRGB. I found this discussion, even though it's quite old, the latest version still doesn't seem have B&W color space option for export.

lovell commented 8 years ago

If you want to automagically detect if a 3+ channel "color" image will appear to humans to be grayscale, you could create a copy via the existing grayscale function and apply a color-distance algorithm between it and the original image.

There's a hidden, internal API used by the automated tests that does something similar to this - see https://github.com/lovell/sharp/blob/master/test/fixtures/index.js#L131 - please remember this is not external API and may change/break without warning.

rayshan commented 8 years ago

Thanks for the tip Lovell. I processed 60 files of various sizes and colors, some grayscale only. The code looks something like this:

sharp(chunk.contents) // buffer
    .grayscale()
    .toFile("tmp/temp.jpg")
    .then(() => {
        console.log(maxColourDistance(chunk.path, "tmp/temp.jpg"));
    });

Results sorted:

7.667830467
8.346505165
9.105529785
9.418406487
9.86065197
9.863577843
10.42674351
10.76643562

**potential threshold**

18.11822319
18.70937729
24.48128891
25.24388504
25.27663612
25.8201046
26.00285912
26.77753067
26.92282677
27.30444717
27.64958382
27.71055222
28.12445068
28.34516907
28.460886
28.47734451
28.47946167
28.5562458
28.89081383
28.99706078
29.10570908
29.20390892
29.5150032
29.57815933
29.60145187
29.65199089
29.66287231
29.68298721
29.71682739
29.73700905
29.74423218
29.75669479
29.83790588
29.91073418
29.92290497
30.26291084
30.3011837
30.30294609
30.62481308
30.88695145
30.95523071
31.252985
31.32004547
31.33866501
31.34113693
31.41125298
31.86034775
32.4184494
32.60464478
32.74927139
34.97540283
35.71175003

I'm thinking 15 is a good threshold. The image with a value of 18 or so is below, and I can see how it's close to the threshold:

our omnipotent overlord - thumbnail - 1x

It takes about 1~2 seconds per image, depending on size. It's slightly slower than using a pure JavaScript solution like https://github.com/Pomax/RGBAnalyse. Performance will likely improve drastically without the extra 1x file write and 2x file read. Without optimization and a public API, I'll likely stick with RGBAnalyse for now, though I still need sharp for resizing and whatnot.

I'll leave it up to you to close this ticket, in case you envision sharp to take on such analysis functions.

lovell commented 8 years ago

Given it's only used by some of the tests, the internal _maxColourDistance function hasn't been optimised and could probably be improved by a factor of at least 5x with only a small decrease in accuracy.

I'll leave this task open as being possible future external API to see how much support it gathers. Thanks for the original suggestion!

felicks commented 5 years ago

@lovell, @rayshan thanks for this. I want to use the maxColourDistance function, but I'm not able to. It says maxColourDistance is not defined.

lovell commented 5 years ago

@felicks This discussion relates to a future possible enhancement to make an existing internal, private function part of the external API, which is why the code examples above do not currently work.

jcupitt commented 4 years ago

This is usually done with Spatial CIELAB:

http://scarlet.stanford.edu/~brian/scielab/scielab.html

The basic idea is simple: blur before calculating CIELAB, so there's a spatial component too.

In pyvips it would be something like:

def scielab(a, b):
    return a.gaussblur(2).dE76(b.gaussblur(2))

So you'd just test for scielab(a, grey).max() < 10.

libvips is missing an operator to calculate dE76 for (image, constant) -- that would be a useful addition for something like this.