mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add most dominant colour to image stats #640
This commit is contained in:
parent
dcc42f8514
commit
c42de19d2a
@ -72,6 +72,7 @@ A `Promise` is returned when `callback` is not provided.
|
|||||||
- `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
- `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
||||||
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
|
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
|
||||||
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
||||||
|
- `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
@ -89,7 +90,8 @@ image
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { entropy, sharpness } = await sharp(input).stats();
|
const { entropy, sharpness, dominant } = await sharp(input).stats();
|
||||||
|
const { r, g, b } = dominant;
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns **[Promise][5]<[Object][6]>**
|
Returns **[Promise][5]<[Object][6]>**
|
||||||
|
@ -12,6 +12,9 @@ Requires libvips v8.10.0
|
|||||||
|
|
||||||
* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`.
|
* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`.
|
||||||
|
|
||||||
|
* Add most `dominant` colour to image `stats`.
|
||||||
|
[#640](https://github.com/lovell/sharp/issues/640)
|
||||||
|
|
||||||
## v0.25 - *yield*
|
## v0.25 - *yield*
|
||||||
|
|
||||||
Requires libvips v8.9.1
|
Requires libvips v8.9.1
|
||||||
|
@ -300,6 +300,7 @@ function metadata (callback) {
|
|||||||
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
|
||||||
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
|
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
|
||||||
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
|
||||||
|
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const image = sharp(inputJpg);
|
* const image = sharp(inputJpg);
|
||||||
@ -310,7 +311,8 @@ function metadata (callback) {
|
|||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const { entropy, sharpness } = await sharp(input).stats();
|
* const { entropy, sharpness, dominant } = await sharp(input).stats();
|
||||||
|
* const { r, g, b } = dominant;
|
||||||
*
|
*
|
||||||
* @param {Function} [callback] - called with the arguments `(err, stats)`
|
* @param {Function} [callback] - called with the arguments `(err, stats)`
|
||||||
* @returns {Promise<Object>}
|
* @returns {Promise<Object>}
|
||||||
|
@ -724,4 +724,25 @@ namespace sharp {
|
|||||||
return std::make_tuple(image, alphaColour);
|
return std::make_tuple(image, alphaColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes alpha channel, if any.
|
||||||
|
*/
|
||||||
|
VImage RemoveAlpha(VImage image) {
|
||||||
|
if (HasAlpha(image)) {
|
||||||
|
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ensures alpha channel, if missing.
|
||||||
|
*/
|
||||||
|
VImage EnsureAlpha(VImage image) {
|
||||||
|
if (!HasAlpha(image)) {
|
||||||
|
std::vector<double> alpha;
|
||||||
|
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
|
||||||
|
image = image.bandjoin_const(alpha);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
10
src/common.h
10
src/common.h
@ -271,6 +271,16 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes alpha channel, if any.
|
||||||
|
*/
|
||||||
|
VImage RemoveAlpha(VImage image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ensures alpha channel, if missing.
|
||||||
|
*/
|
||||||
|
VImage EnsureAlpha(VImage image);
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
#endif // SRC_COMMON_H_
|
#endif // SRC_COMMON_H_
|
||||||
|
@ -27,29 +27,6 @@ using vips::VImage;
|
|||||||
using vips::VError;
|
using vips::VError;
|
||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
/*
|
|
||||||
Removes alpha channel, if any.
|
|
||||||
*/
|
|
||||||
VImage RemoveAlpha(VImage image) {
|
|
||||||
if (HasAlpha(image)) {
|
|
||||||
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
|
|
||||||
}
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Ensures alpha channel, if missing.
|
|
||||||
*/
|
|
||||||
VImage EnsureAlpha(VImage image) {
|
|
||||||
if (!HasAlpha(image)) {
|
|
||||||
std::vector<double> alpha;
|
|
||||||
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
|
|
||||||
image = image.bandjoin_const(alpha);
|
|
||||||
}
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tint an image using the specified chroma, preserving the original image luminance
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
*/
|
*/
|
||||||
|
@ -25,16 +25,6 @@ using vips::VImage;
|
|||||||
|
|
||||||
namespace sharp {
|
namespace sharp {
|
||||||
|
|
||||||
/*
|
|
||||||
Removes alpha channel, if any.
|
|
||||||
*/
|
|
||||||
VImage RemoveAlpha(VImage image);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Ensures alpha channel, if missing.
|
|
||||||
*/
|
|
||||||
VImage EnsureAlpha(VImage image);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tint an image using the specified chroma, preserving the original image luminance
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
*/
|
*/
|
||||||
|
17
src/stats.cc
17
src/stats.cc
@ -86,6 +86,18 @@ class StatsWorker : public Napi::AsyncWorker {
|
|||||||
0.0, 1.0, 0.0);
|
0.0, 1.0, 0.0);
|
||||||
laplacian.set("scale", 9.0);
|
laplacian.set("scale", 9.0);
|
||||||
baton->sharpness = greyscale.conv(laplacian).deviate();
|
baton->sharpness = greyscale.conv(laplacian).deviate();
|
||||||
|
// Most dominant sRGB colour via 4096-bin 3D histogram
|
||||||
|
vips::VImage hist = sharp::RemoveAlpha(image)
|
||||||
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||||
|
.hist_find_ndim(VImage::option()->set("bins", 16));
|
||||||
|
std::complex<double> maxpos = hist.maxpos();
|
||||||
|
int const dx = static_cast<int>(std::real(maxpos));
|
||||||
|
int const dy = static_cast<int>(std::imag(maxpos));
|
||||||
|
std::vector<double> pel = hist(dx, dy);
|
||||||
|
int const dz = std::distance(pel.begin(), std::find(pel.begin(), pel.end(), hist.max()));
|
||||||
|
baton->dominantRed = dx * 16 + 8;
|
||||||
|
baton->dominantGreen = dy * 16 + 8;
|
||||||
|
baton->dominantBlue = dz * 16 + 8;
|
||||||
} catch (vips::VError const &err) {
|
} catch (vips::VError const &err) {
|
||||||
(baton->err).append(err.what());
|
(baton->err).append(err.what());
|
||||||
}
|
}
|
||||||
@ -133,6 +145,11 @@ class StatsWorker : public Napi::AsyncWorker {
|
|||||||
info.Set("isOpaque", baton->isOpaque);
|
info.Set("isOpaque", baton->isOpaque);
|
||||||
info.Set("entropy", baton->entropy);
|
info.Set("entropy", baton->entropy);
|
||||||
info.Set("sharpness", baton->sharpness);
|
info.Set("sharpness", baton->sharpness);
|
||||||
|
Napi::Object dominant = Napi::Object::New(env);
|
||||||
|
dominant.Set("r", baton->dominantRed);
|
||||||
|
dominant.Set("g", baton->dominantGreen);
|
||||||
|
dominant.Set("b", baton->dominantBlue);
|
||||||
|
info.Set("dominant", dominant);
|
||||||
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
||||||
} else {
|
} else {
|
||||||
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
|
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
|
||||||
|
@ -48,6 +48,9 @@ struct StatsBaton {
|
|||||||
bool isOpaque;
|
bool isOpaque;
|
||||||
double entropy;
|
double entropy;
|
||||||
double sharpness;
|
double sharpness;
|
||||||
|
int dominantRed;
|
||||||
|
int dominantGreen;
|
||||||
|
int dominantBlue;
|
||||||
|
|
||||||
std::string err;
|
std::string err;
|
||||||
|
|
||||||
@ -55,7 +58,10 @@ struct StatsBaton {
|
|||||||
input(nullptr),
|
input(nullptr),
|
||||||
isOpaque(true),
|
isOpaque(true),
|
||||||
entropy(0.0),
|
entropy(0.0),
|
||||||
sharpness(0.0)
|
sharpness(0.0),
|
||||||
|
dominantRed(0),
|
||||||
|
dominantGreen(0),
|
||||||
|
dominantBlue(0)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,6 +27,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.7883011147075762));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.7883011147075762));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(40, r);
|
||||||
|
assert.strictEqual(40, g);
|
||||||
|
assert.strictEqual(40, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
@ -87,6 +92,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 9.111356137722868));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 9.111356137722868));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(248, r);
|
||||||
|
assert.strictEqual(248, g);
|
||||||
|
assert.strictEqual(248, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
@ -114,6 +124,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 2.522916068931278));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 2.522916068931278));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(248, r);
|
||||||
|
assert.strictEqual(248, g);
|
||||||
|
assert.strictEqual(248, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
@ -190,6 +205,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(72, r);
|
||||||
|
assert.strictEqual(104, g);
|
||||||
|
assert.strictEqual(72, b);
|
||||||
|
|
||||||
// alpha channel
|
// alpha channel
|
||||||
assert.strictEqual(0, stats.channels[3].min);
|
assert.strictEqual(0, stats.channels[3].min);
|
||||||
assert.strictEqual(0, stats.channels[3].max);
|
assert.strictEqual(0, stats.channels[3].max);
|
||||||
@ -218,6 +238,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 10.312521863719589));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 10.312521863719589));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(248, r);
|
||||||
|
assert.strictEqual(248, g);
|
||||||
|
assert.strictEqual(248, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
@ -246,6 +271,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 9.959951636662941));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 9.959951636662941));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(40, r);
|
||||||
|
assert.strictEqual(136, g);
|
||||||
|
assert.strictEqual(200, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(true, isInRange(stats.channels[0].max, 254, 255));
|
assert.strictEqual(true, isInRange(stats.channels[0].max, 254, 255));
|
||||||
@ -306,6 +336,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 2.9250574456255682));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 2.9250574456255682));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(120, r);
|
||||||
|
assert.strictEqual(136, g);
|
||||||
|
assert.strictEqual(88, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(35, stats.channels[0].min);
|
assert.strictEqual(35, stats.channels[0].min);
|
||||||
assert.strictEqual(254, stats.channels[0].max);
|
assert.strictEqual(254, stats.channels[0].max);
|
||||||
@ -366,6 +401,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 15.870619016486861));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 15.870619016486861));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(8, r);
|
||||||
|
assert.strictEqual(8, g);
|
||||||
|
assert.strictEqual(8, b);
|
||||||
|
|
||||||
// gray channel
|
// gray channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(101, stats.channels[0].max);
|
assert.strictEqual(101, stats.channels[0].max);
|
||||||
@ -411,6 +451,17 @@ describe('Image Stats', function () {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('Dominant colour', () =>
|
||||||
|
sharp(fixtures.inputJpgBooleanTest)
|
||||||
|
.stats()
|
||||||
|
.then(({ dominant }) => {
|
||||||
|
const { r, g, b } = dominant;
|
||||||
|
assert.strictEqual(r, 8);
|
||||||
|
assert.strictEqual(g, 136);
|
||||||
|
assert.strictEqual(b, 248);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
it('Stream in, Callback out', function (done) {
|
it('Stream in, Callback out', function (done) {
|
||||||
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) {
|
||||||
@ -420,6 +471,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(40, r);
|
||||||
|
assert.strictEqual(40, g);
|
||||||
|
assert.strictEqual(40, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
@ -483,6 +539,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(40, r);
|
||||||
|
assert.strictEqual(40, g);
|
||||||
|
assert.strictEqual(40, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
@ -541,6 +602,11 @@ describe('Image Stats', function () {
|
|||||||
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
|
||||||
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
|
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
|
||||||
|
|
||||||
|
const { r, g, b } = stats.dominant;
|
||||||
|
assert.strictEqual(40, r);
|
||||||
|
assert.strictEqual(40, g);
|
||||||
|
assert.strictEqual(40, b);
|
||||||
|
|
||||||
// red channel
|
// red channel
|
||||||
assert.strictEqual(0, stats.channels[0].min);
|
assert.strictEqual(0, stats.channels[0].min);
|
||||||
assert.strictEqual(255, stats.channels[0].max);
|
assert.strictEqual(255, stats.channels[0].max);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user