Skip to content

Commit

Permalink
Merge 7ef26b1 into aad16ac
Browse files Browse the repository at this point in the history
  • Loading branch information
RNanwani committed Nov 9, 2017
2 parents aad16ac + 7ef26b1 commit efca7da
Show file tree
Hide file tree
Showing 9 changed files with 817 additions and 0 deletions.
1 change: 1 addition & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
'sources': [
'src/common.cc',
'src/metadata.cc',
'src/stats.cc',
'src/operations.cc',
'src/pipeline.cc',
'src/sharp.cc',
Expand Down
37 changes: 37 additions & 0 deletions docs/api-input.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [clone](#clone)
- [metadata](#metadata)
- [stats](#stats)
- [limitInputPixels](#limitinputpixels)
- [sequentialRead](#sequentialread)

Expand Down Expand Up @@ -67,6 +68,42 @@ image

Returns **([Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> | Sharp)**

## stats

Access to pixel-derived image statistics for every channel in the image
A Promises/A+ promise is returned when `callback` is not provided.

- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
- `min` (minimum value in the channel)
- `max` (maximum value in the channel)
- `sum` (sum of all values in a channel)
- `squaresSum` (sum of squared values in a channel)
- `mean` (mean of the values in a channel)
- `stdev` (standard deviation for the values in a channel)
- `minX` (x-coordinate of one of the pixel where the minimum lies)
- `minY` (y-coordinate of one of the pixel where the minimum 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)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel

**Parameters**

- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** called with the arguments `(err, metadata)`

**Examples**

```javascript
const image = sharp(inputJpg);
image
.stats()
.then(function(stats) {
// stats contains the channel-wise statistics array and the isOpaque value
})
```

Returns **([Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> | Sharp)**


## limitInputPixels

Do not process input images where the number of pixels (width _ height) exceeds this limit.
Expand Down
41 changes: 41 additions & 0 deletions lib/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,46 @@ function metadata (callback) {
}
}

function stats (callback) {
const that = this;
if (is.fn(callback)) {
if (this._isStreamInput()) {
this.on('finish', function () {
that._flattenBufferIn();
sharp.stats(that.options, callback);
});
} else {
sharp.stats(this.options, callback);
}
return this;
} else {
if (this._isStreamInput()) {
return new Promise(function (resolve, reject) {
that.on('finish', function () {
that._flattenBufferIn();
sharp.stats(that.options, function (err, stats) {
if (err) {
reject(err);
} else {
resolve(stats);
}
});
});
});
} else {
return new Promise(function (resolve, reject) {
sharp.stats(that.options, function (err, stats) {
if (err) {
reject(err);
} else {
resolve(stats);
}
});
});
}
}
}

/**
* Do not process input images where the number of pixels (width * height) exceeds this limit.
* Assumes image dimensions contained in the input metadata can be trusted.
Expand Down Expand Up @@ -281,6 +321,7 @@ module.exports = function (Sharp) {
// Public
clone,
metadata,
stats,
limitInputPixels,
sequentialRead
].forEach(function (f) {
Expand Down
3 changes: 3 additions & 0 deletions src/sharp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "metadata.h"
#include "pipeline.h"
#include "utilities.h"
#include "stats.h"

NAN_MODULE_INIT(init) {
vips_init("sharp");
Expand All @@ -46,6 +47,8 @@ NAN_MODULE_INIT(init) {
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
Nan::Set(target, Nan::New("stats").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(stats)).ToLocalChecked());
}

NODE_MODULE(sharp, init)
182 changes: 182 additions & 0 deletions src/stats.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <numeric>
#include <vector>
#include <iostream>

#include <node.h>
#include <nan.h>
#include <vips/vips8>

#include "common.h"
#include "stats.h"

class StatsWorker : public Nan::AsyncWorker {
public:
StatsWorker(
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback), baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~StatsWorker() {}

const int STAT_MIN_INDEX = 0;
const int STAT_MAX_INDEX = 1;
const int STAT_SUM_INDEX = 2;
const int STAT_SQ_SUM_INDEX = 3;
const int STAT_MEAN_INDEX = 4;
const int STAT_STDEV_INDEX = 5;
const int STAT_MINX_INDEX = 6;
const int STAT_MINY_INDEX = 7;
const int STAT_MAXX_INDEX = 8;
const int STAT_MAXY_INDEX = 9;

void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;

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

try {
std::tie(image, imageType) = OpenInput(baton->input, VIPS_ACCESS_SEQUENTIAL);
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
if (imageType != sharp::ImageType::UNKNOWN) {
try {
stats = image.stats();
int bands = image.bands();
double const max = MaximumImageAlpha(image.interpretation());
for (int b = 1; b <= bands; b++) {
ChannelStats cStats(stats.getpoint(STAT_MIN_INDEX, b).front(), stats.getpoint(STAT_MAX_INDEX, b).front(),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(),
stats.getpoint(STAT_MINX_INDEX, b).front(), stats.getpoint(STAT_MINY_INDEX, b).front(),
stats.getpoint(STAT_MAXX_INDEX, b).front(), 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;
}
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
}

// Clean up
vips_error_clear();
vips_thread_shutdown();
}

void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();

v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Stats Object
v8::Local<v8::Object> info = New<v8::Object>();
v8::Local<v8::Array> channels = New<v8::Array>();

std::vector<ChannelStats>::iterator it;
int i = 0;
for (it=baton->channelStats.begin() ; it < baton->channelStats.end(); it++, i++) {
v8::Local<v8::Object> channelStat = New<v8::Object>();
Set(channelStat, New("min").ToLocalChecked(), New<v8::Number>(it->min));
Set(channelStat, New("max").ToLocalChecked(), New<v8::Number>(it->max));
Set(channelStat, New("sum").ToLocalChecked(), New<v8::Number>(it->sum));
Set(channelStat, New("squaresSum").ToLocalChecked(), New<v8::Number>(it->squaresSum));
Set(channelStat, New("mean").ToLocalChecked(), New<v8::Number>(it->mean));
Set(channelStat, New("stdev").ToLocalChecked(), New<v8::Number>(it->stdev));
Set(channelStat, New("minX").ToLocalChecked(), New<v8::Number>(it->minX));
Set(channelStat, New("minY").ToLocalChecked(), New<v8::Number>(it->minY));
Set(channelStat, New("maxX").ToLocalChecked(), New<v8::Number>(it->maxX));
Set(channelStat, New("maxY").ToLocalChecked(), New<v8::Number>(it->maxY));
channels->Set(i, channelStat);
}

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

// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;

// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message);
warning = sharp::VipsWarningPop();
}

// Return to JavaScript
callback->Call(2, argv);
}

private:
StatsBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
};

/*
stats(options, callback)
*/
NAN_METHOD(stats) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;

// V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();

// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);

// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));

// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist));

// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
}
64 changes: 64 additions & 0 deletions src/stats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef SRC_STATS_H_
#define SRC_STATS_H_

#include <string>
#include <nan.h>

#include "./common.h"

struct ChannelStats {
// stats per channel
int min;
int max;
double sum;
double squaresSum;
double mean;
double stdev;
int minX;
int minY;
int maxX;
int maxY;

ChannelStats():
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0)
, minX(0), minY(0), maxX(0), maxY(0) {}

ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
};

struct StatsBaton {
// Input
sharp::InputDescriptor *input;

// Output
std::vector<ChannelStats> channelStats;
bool isOpaque;

std::string err;

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

NAN_METHOD(stats);

#endif // SRC_STATS_H_
Binary file added test/fixtures/full-transparent.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module.exports = {

inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngCompleteTransparency: getPath('full-transparent.png'),
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
inputPngWithOneColor: getPath('2x2_fdcce6.png'),
inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
Expand Down

0 comments on commit efca7da

Please sign in to comment.