Skip to content

Commit

Permalink
Add experimental entropy field to stats response
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Aug 6, 2018
1 parent 532de4e commit 25bd2ce
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Requires libvips v8.6.1.
* Improve install time error messages for FreeBSD users.
[#1310](https://github.com/lovell/sharp/issues/1310)

* Add experimental entropy field to stats response.

#### v0.20.5 - 27<sup>th</sup> June 2018

* Expose libjpeg optimize_coding flag.
Expand Down
1 change: 1 addition & 0 deletions lib/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ function metadata (callback) {
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
*
* @example
* const image = sharp(inputJpg);
Expand Down
19 changes: 11 additions & 8 deletions src/stats.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ class StatsWorker : public Nan::AsyncWorker {
using sharp::MaximumImageAlpha;

vips::VImage image;
vips::VImage stats;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;

try {
Expand All @@ -69,9 +68,8 @@ class StatsWorker : public Nan::AsyncWorker {
}
if (imageType != sharp::ImageType::UNKNOWN) {
try {
stats = image.stats();
int bands = image.bands();
double const max = MaximumImageAlpha(image.interpretation());
vips::VImage stats = image.stats();
int const bands = image.bands();
for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
Expand All @@ -83,11 +81,15 @@ class StatsWorker : public Nan::AsyncWorker {
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
baton->channelStats.push_back(cStats);
}

// alpha layer is there and the last band i.e. alpha has its max value greater than 0)
if (sharp::HasAlpha(image) && stats.getpoint(STAT_MIN_INDEX, bands).front() != max) {
baton->isOpaque = false;
// Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false;
}
}
// Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(image.colourspace(VIPS_INTERPRETATION_B_W)[0].hist_find().hist_entropy());
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
Expand Down Expand Up @@ -130,6 +132,7 @@ class StatsWorker : public Nan::AsyncWorker {

Set(info, New("channels").ToLocalChecked(), channels);
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
argv[1] = info;
}

Expand Down
4 changes: 3 additions & 1 deletion src/stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ struct StatsBaton {
// Output
std::vector<ChannelStats> channelStats;
bool isOpaque;
double entropy;

std::string err;

StatsBaton():
input(nullptr),
isOpaque(true)
isOpaque(true),
entropy(0.0)
{}
};

Expand Down
15 changes: 15 additions & 0 deletions test/unit/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('Image Stats', function () {
if (err) throw err;

assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down Expand Up @@ -82,6 +83,7 @@ describe('Image Stats', function () {
if (err) throw err;

assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand All @@ -105,7 +107,9 @@ describe('Image Stats', function () {
it('PNG with transparency', function (done) {
sharp(fixtures.inputPngWithTransparency).stats(function (err, stats) {
if (err) throw err;

assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down Expand Up @@ -180,6 +184,7 @@ describe('Image Stats', function () {
if (err) throw err;

assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));

// alpha channel
assert.strictEqual(0, stats.channels[3]['min']);
Expand All @@ -204,7 +209,9 @@ describe('Image Stats', function () {
it('Tiff', function (done) {
sharp(fixtures.inputTiff).stats(function (err, stats) {
if (err) throw err;

assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand All @@ -231,6 +238,7 @@ describe('Image Stats', function () {
if (err) throw err;

assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down Expand Up @@ -289,6 +297,7 @@ describe('Image Stats', function () {
if (err) throw err;

assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));

// red channel
assert.strictEqual(35, stats.channels[0]['min']);
Expand Down Expand Up @@ -345,7 +354,9 @@ describe('Image Stats', function () {
it('Grayscale GIF with alpha', function (done) {
sharp(fixtures.inputGifGreyPlusAlpha).stats(function (err, stats) {
if (err) throw err;

assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));

// gray channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down Expand Up @@ -387,7 +398,9 @@ describe('Image Stats', function () {
const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().stats(function (err, stats) {
if (err) throw err;

assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down Expand Up @@ -449,6 +462,7 @@ describe('Image Stats', function () {

return pipeline.stats().then(function (stats) {
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down Expand Up @@ -505,6 +519,7 @@ describe('Image Stats', function () {
it('File in, Promise out', function () {
return sharp(fixtures.inputJpg).stats().then(function (stats) {
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));

// red channel
assert.strictEqual(0, stats.channels[0]['min']);
Expand Down

0 comments on commit 25bd2ce

Please sign in to comment.