mirror of
https://github.com/lovell/sharp.git
synced 2025-07-11 19:40:14 +02:00
Add experimental entropy field to stats response
This commit is contained in:
parent
532de4ecab
commit
25bd2cea3e
@ -23,6 +23,8 @@ Requires libvips v8.6.1.
|
|||||||
* Improve install time error messages for FreeBSD users.
|
* Improve install time error messages for FreeBSD users.
|
||||||
[#1310](https://github.com/lovell/sharp/issues/1310)
|
[#1310](https://github.com/lovell/sharp/issues/1310)
|
||||||
|
|
||||||
|
* Add experimental entropy field to stats response.
|
||||||
|
|
||||||
#### v0.20.5 - 27<sup>th</sup> June 2018
|
#### v0.20.5 - 27<sup>th</sup> June 2018
|
||||||
|
|
||||||
* Expose libjpeg optimize_coding flag.
|
* Expose libjpeg optimize_coding flag.
|
||||||
|
@ -264,6 +264,7 @@ function metadata (callback) {
|
|||||||
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
|
||||||
* - `maxY` (y-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
|
* - `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
|
* @example
|
||||||
* const image = sharp(inputJpg);
|
* const image = sharp(inputJpg);
|
||||||
|
19
src/stats.cc
19
src/stats.cc
@ -59,7 +59,6 @@ class StatsWorker : public Nan::AsyncWorker {
|
|||||||
using sharp::MaximumImageAlpha;
|
using sharp::MaximumImageAlpha;
|
||||||
|
|
||||||
vips::VImage image;
|
vips::VImage image;
|
||||||
vips::VImage stats;
|
|
||||||
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
|
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -69,9 +68,8 @@ class StatsWorker : public Nan::AsyncWorker {
|
|||||||
}
|
}
|
||||||
if (imageType != sharp::ImageType::UNKNOWN) {
|
if (imageType != sharp::ImageType::UNKNOWN) {
|
||||||
try {
|
try {
|
||||||
stats = image.stats();
|
vips::VImage stats = image.stats();
|
||||||
int bands = image.bands();
|
int const bands = image.bands();
|
||||||
double const max = MaximumImageAlpha(image.interpretation());
|
|
||||||
for (int b = 1; b <= bands; b++) {
|
for (int b = 1; b <= bands; b++) {
|
||||||
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
|
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
|
||||||
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
|
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
|
||||||
@ -83,11 +81,15 @@ class StatsWorker : public Nan::AsyncWorker {
|
|||||||
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
|
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
|
||||||
baton->channelStats.push_back(cStats);
|
baton->channelStats.push_back(cStats);
|
||||||
}
|
}
|
||||||
|
// Image is not opaque when alpha layer is present and contains a non-mamixa value
|
||||||
// alpha layer is there and the last band i.e. alpha has its max value greater than 0)
|
if (sharp::HasAlpha(image)) {
|
||||||
if (sharp::HasAlpha(image) && stats.getpoint(STAT_MIN_INDEX, bands).front() != max) {
|
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
|
||||||
baton->isOpaque = false;
|
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) {
|
} catch (vips::VError const &err) {
|
||||||
(baton->err).append(err.what());
|
(baton->err).append(err.what());
|
||||||
}
|
}
|
||||||
@ -130,6 +132,7 @@ class StatsWorker : public Nan::AsyncWorker {
|
|||||||
|
|
||||||
Set(info, New("channels").ToLocalChecked(), channels);
|
Set(info, New("channels").ToLocalChecked(), channels);
|
||||||
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
|
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
|
||||||
|
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
|
||||||
argv[1] = info;
|
argv[1] = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,12 +51,14 @@ struct StatsBaton {
|
|||||||
// Output
|
// Output
|
||||||
std::vector<ChannelStats> channelStats;
|
std::vector<ChannelStats> channelStats;
|
||||||
bool isOpaque;
|
bool isOpaque;
|
||||||
|
double entropy;
|
||||||
|
|
||||||
std::string err;
|
std::string err;
|
||||||
|
|
||||||
StatsBaton():
|
StatsBaton():
|
||||||
input(nullptr),
|
input(nullptr),
|
||||||
isOpaque(true)
|
isOpaque(true),
|
||||||
|
entropy(0.0)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ describe('Image Stats', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -82,6 +83,7 @@ describe('Image Stats', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -105,7 +107,9 @@ describe('Image Stats', function () {
|
|||||||
it('PNG with transparency', function (done) {
|
it('PNG with transparency', function (done) {
|
||||||
sharp(fixtures.inputPngWithTransparency).stats(function (err, stats) {
|
sharp(fixtures.inputPngWithTransparency).stats(function (err, stats) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(false, stats.isOpaque);
|
assert.strictEqual(false, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -180,6 +184,7 @@ describe('Image Stats', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(false, stats.isOpaque);
|
assert.strictEqual(false, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));
|
||||||
|
|
||||||
// alpha channel
|
// alpha channel
|
||||||
assert.strictEqual(0, stats.channels[3]['min']);
|
assert.strictEqual(0, stats.channels[3]['min']);
|
||||||
@ -204,7 +209,9 @@ describe('Image Stats', function () {
|
|||||||
it('Tiff', function (done) {
|
it('Tiff', function (done) {
|
||||||
sharp(fixtures.inputTiff).stats(function (err, stats) {
|
sharp(fixtures.inputTiff).stats(function (err, stats) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -231,6 +238,7 @@ describe('Image Stats', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -289,6 +297,7 @@ describe('Image Stats', function () {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(35, stats.channels[0]['min']);
|
assert.strictEqual(35, stats.channels[0]['min']);
|
||||||
@ -345,7 +354,9 @@ describe('Image Stats', function () {
|
|||||||
it('Grayscale GIF with alpha', function (done) {
|
it('Grayscale GIF with alpha', function (done) {
|
||||||
sharp(fixtures.inputGifGreyPlusAlpha).stats(function (err, stats) {
|
sharp(fixtures.inputGifGreyPlusAlpha).stats(function (err, stats) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(false, stats.isOpaque);
|
assert.strictEqual(false, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));
|
||||||
|
|
||||||
// gray channel
|
// gray channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -387,7 +398,9 @@ describe('Image Stats', function () {
|
|||||||
const readable = fs.createReadStream(fixtures.inputJpg);
|
const readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
const pipeline = sharp().stats(function (err, stats) {
|
const pipeline = sharp().stats(function (err, stats) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -449,6 +462,7 @@ describe('Image Stats', function () {
|
|||||||
|
|
||||||
return pipeline.stats().then(function (stats) {
|
return pipeline.stats().then(function (stats) {
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
@ -505,6 +519,7 @@ describe('Image Stats', function () {
|
|||||||
it('File in, Promise out', function () {
|
it('File in, Promise out', function () {
|
||||||
return sharp(fixtures.inputJpg).stats().then(function (stats) {
|
return sharp(fixtures.inputJpg).stats().then(function (stats) {
|
||||||
assert.strictEqual(true, stats.isOpaque);
|
assert.strictEqual(true, stats.isOpaque);
|
||||||
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0]['min']);
|
assert.strictEqual(0, stats.channels[0]['min']);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user