mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Expose libvips gaussnoise operation (#2527)
This commit is contained in:
parent
419cbe50f6
commit
c9f85fe27f
@ -38,6 +38,10 @@ Implements the [stream.Duplex][1] class.
|
|||||||
- `options.create.width` **[number][8]?** integral number of pixels wide.
|
- `options.create.width` **[number][8]?** integral number of pixels wide.
|
||||||
- `options.create.height` **[number][8]?** integral number of pixels high.
|
- `options.create.height` **[number][8]?** integral number of pixels high.
|
||||||
- `options.create.channels` **[number][8]?** integral number of channels, either 3 (RGB) or 4 (RGBA).
|
- `options.create.channels` **[number][8]?** integral number of channels, either 3 (RGB) or 4 (RGBA).
|
||||||
|
- `options.create.noise` **[Object][6]?** describes a noise to be created.
|
||||||
|
- `options.create.noise.type` **[string][5]?** type of generated noise. (supported: `gaussian`)
|
||||||
|
- `options.create.noise.mean` **[number][8]?** mean of pixels in generated image.
|
||||||
|
- `options.create.noise.sigma` **[number][8]?** standard deviation of pixels in generated image.
|
||||||
- `options.create.background` **([string][5] \| [Object][6])?** parsed by the [color][9] module to extract values for red, green, blue and alpha.
|
- `options.create.background` **([string][5] \| [Object][6])?** parsed by the [color][9] module to extract values for red, green, blue and alpha.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
48
lib/input.js
48
lib/input.js
@ -137,22 +137,50 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
is.object(inputOptions.create) &&
|
is.object(inputOptions.create) &&
|
||||||
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
|
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
|
||||||
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
|
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
|
||||||
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
|
is.integer(inputOptions.create.channels)
|
||||||
is.defined(inputOptions.create.background)
|
|
||||||
) {
|
) {
|
||||||
inputDescriptor.createWidth = inputOptions.create.width;
|
inputDescriptor.createWidth = inputOptions.create.width;
|
||||||
inputDescriptor.createHeight = inputOptions.create.height;
|
inputDescriptor.createHeight = inputOptions.create.height;
|
||||||
inputDescriptor.createChannels = inputOptions.create.channels;
|
inputDescriptor.createChannels = inputOptions.create.channels;
|
||||||
const background = color(inputOptions.create.background);
|
// Noise
|
||||||
inputDescriptor.createBackground = [
|
if (is.defined(inputOptions.create.noise)) {
|
||||||
background.red(),
|
if (!is.object(inputOptions.create.noise)) {
|
||||||
background.green(),
|
throw new Error('Expected noise to be an object');
|
||||||
background.blue(),
|
}
|
||||||
Math.round(background.alpha() * 255)
|
if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
|
||||||
];
|
throw new Error('Only gaussian noise is supported at the moment');
|
||||||
|
}
|
||||||
|
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
|
||||||
|
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
|
||||||
|
}
|
||||||
|
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
|
||||||
|
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
|
||||||
|
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
|
||||||
|
}
|
||||||
|
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
|
||||||
|
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
|
||||||
|
}
|
||||||
|
} else if (is.defined(inputOptions.create.background)) {
|
||||||
|
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
||||||
|
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
||||||
|
}
|
||||||
|
const background = color(inputOptions.create.background);
|
||||||
|
inputDescriptor.createBackground = [
|
||||||
|
background.red(),
|
||||||
|
background.green(),
|
||||||
|
background.blue(),
|
||||||
|
Math.round(background.alpha() * 255)
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected valid noise or background to create a new input image');
|
||||||
|
}
|
||||||
delete inputDescriptor.buffer;
|
delete inputDescriptor.buffer;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected valid width, height, channels and background to create a new input image');
|
throw new Error('Expected valid width, height and channels to create a new input image');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (is.defined(inputOptions)) {
|
} else if (is.defined(inputOptions)) {
|
||||||
|
@ -73,7 +73,8 @@
|
|||||||
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
||||||
"Christian Flintrup <chr@gigahost.dk>",
|
"Christian Flintrup <chr@gigahost.dk>",
|
||||||
"Manan Jadhav <manan@motionden.com>",
|
"Manan Jadhav <manan@motionden.com>",
|
||||||
"Leon Radley <leon@radley.se>"
|
"Leon Radley <leon@radley.se>",
|
||||||
|
"alza54 <alza54@thiocod.in>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||||
|
@ -109,7 +109,13 @@ namespace sharp {
|
|||||||
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
||||||
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
||||||
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
||||||
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
if (HasAttr(input, "createNoiseType")) {
|
||||||
|
descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
|
||||||
|
descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
|
||||||
|
descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
|
||||||
|
} else {
|
||||||
|
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Limit input images to a given number of pixels, where pixels = width * height
|
// Limit input images to a given number of pixels, where pixels = width * height
|
||||||
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
||||||
@ -318,15 +324,35 @@ namespace sharp {
|
|||||||
} else {
|
} else {
|
||||||
if (descriptor->createChannels > 0) {
|
if (descriptor->createChannels > 0) {
|
||||||
// Create new image
|
// Create new image
|
||||||
std::vector<double> background = {
|
if (descriptor->createNoiseType == "gaussian") {
|
||||||
descriptor->createBackground[0],
|
int const channels = descriptor->createChannels;
|
||||||
descriptor->createBackground[1],
|
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
|
||||||
descriptor->createBackground[2]
|
std::vector<VImage> bands = {};
|
||||||
};
|
bands.reserve(channels);
|
||||||
if (descriptor->createChannels == 4) {
|
for (int _band = 0; _band < channels; _band++) {
|
||||||
background.push_back(descriptor->createBackground[3]);
|
bands.push_back(image.gaussnoise(
|
||||||
|
descriptor->createWidth,
|
||||||
|
descriptor->createHeight,
|
||||||
|
VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
|
||||||
|
}
|
||||||
|
image = image.bandjoin(bands);
|
||||||
|
image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
|
||||||
|
if (channels < 3) {
|
||||||
|
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
||||||
|
} else {
|
||||||
|
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::vector<double> background = {
|
||||||
|
descriptor->createBackground[0],
|
||||||
|
descriptor->createBackground[1],
|
||||||
|
descriptor->createBackground[2]
|
||||||
|
};
|
||||||
|
if (descriptor->createChannels == 4) {
|
||||||
|
background.push_back(descriptor->createBackground[3]);
|
||||||
|
}
|
||||||
|
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
||||||
}
|
}
|
||||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
|
||||||
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
||||||
imageType = ImageType::RAW;
|
imageType = ImageType::RAW;
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,6 +64,9 @@ namespace sharp {
|
|||||||
int createWidth;
|
int createWidth;
|
||||||
int createHeight;
|
int createHeight;
|
||||||
std::vector<double> createBackground;
|
std::vector<double> createBackground;
|
||||||
|
std::string createNoiseType;
|
||||||
|
double createNoiseMean;
|
||||||
|
double createNoiseSigma;
|
||||||
|
|
||||||
InputDescriptor():
|
InputDescriptor():
|
||||||
buffer(nullptr),
|
buffer(nullptr),
|
||||||
@ -82,7 +85,9 @@ namespace sharp {
|
|||||||
createChannels(0),
|
createChannels(0),
|
||||||
createWidth(0),
|
createWidth(0),
|
||||||
createHeight(0),
|
createHeight(0),
|
||||||
createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
|
createBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||||
|
createNoiseMean(0.0),
|
||||||
|
createNoiseSigma(0.0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convenience methods to access the attributes of a Napi::Object
|
// Convenience methods to access the attributes of a Napi::Object
|
||||||
|
258
test/unit/noise.js
Normal file
258
test/unit/noise.js
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('Gaussian noise', function () {
|
||||||
|
it('generate single-channel gaussian noise', function (done) {
|
||||||
|
const output = fixtures.path('output.noise-1-channel.png');
|
||||||
|
const noise = sharp({
|
||||||
|
create: {
|
||||||
|
width: 1024,
|
||||||
|
height: 768,
|
||||||
|
channels: 1, // b-w
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 128,
|
||||||
|
sigma: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
noise.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(1024, info.width);
|
||||||
|
assert.strictEqual(768, info.height);
|
||||||
|
assert.strictEqual(1, info.channels);
|
||||||
|
sharp(output).metadata(function (err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('b-w', metadata.space);
|
||||||
|
assert.strictEqual('uchar', metadata.depth);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generate 3-channels gaussian noise', function (done) {
|
||||||
|
const output = fixtures.path('output.noise-3-channels.png');
|
||||||
|
const noise = sharp({
|
||||||
|
create: {
|
||||||
|
width: 1024,
|
||||||
|
height: 768,
|
||||||
|
channels: 3, // sRGB
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 128,
|
||||||
|
sigma: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
noise.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(1024, info.width);
|
||||||
|
assert.strictEqual(768, info.height);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
sharp(output).metadata(function (err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual('uchar', metadata.depth);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overlay 3-channels gaussian noise over image', function (done) {
|
||||||
|
const output = fixtures.path('output.noise-image.jpg');
|
||||||
|
const noise = sharp({
|
||||||
|
create: {
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
channels: 3,
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 0,
|
||||||
|
sigma: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
noise.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: data,
|
||||||
|
blend: 'exclusion',
|
||||||
|
raw: {
|
||||||
|
width: info.width,
|
||||||
|
height: info.height,
|
||||||
|
channels: info.channels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
// perceptual hashing detects that images are the same (difference is <=1%)
|
||||||
|
fixtures.assertSimilar(output, fixtures.inputJpg, function (err) {
|
||||||
|
if (err) throw err;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('overlay strong single-channel (sRGB) gaussian noise with 25% transparency over transparent png image', function (done) {
|
||||||
|
const output = fixtures.path('output.noise-image-transparent.png');
|
||||||
|
const width = 320;
|
||||||
|
const height = 240;
|
||||||
|
const rawData = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
channels: 1
|
||||||
|
};
|
||||||
|
const noise = sharp({
|
||||||
|
create: {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
channels: 1,
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 200,
|
||||||
|
sigma: 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
noise
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(1, info.channels);
|
||||||
|
sharp(data, { raw: rawData })
|
||||||
|
.joinChannel(data, { raw: rawData }) // r channel
|
||||||
|
.joinChannel(data, { raw: rawData }) // b channel
|
||||||
|
.joinChannel(Buffer.alloc(width * height, 64), { raw: rawData }) // alpha channel
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
sharp(fixtures.inputPngRGBWithAlpha)
|
||||||
|
.resize(width, height)
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: data,
|
||||||
|
blend: 'exclusion',
|
||||||
|
raw: {
|
||||||
|
width: info.width,
|
||||||
|
height: info.height,
|
||||||
|
channels: info.channels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.toFile(output, function (err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(width, info.width);
|
||||||
|
assert.strictEqual(height, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
fixtures.assertSimilar(output, fixtures.inputPngRGBWithAlpha, { threshold: 10 }, function (err) {
|
||||||
|
if (err) throw err;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('no create object properties specified', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp({
|
||||||
|
create: {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid noise object', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp({
|
||||||
|
create: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
channels: 3,
|
||||||
|
noise: 'gaussian'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unknown type of noise', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp({
|
||||||
|
create: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
channels: 3,
|
||||||
|
noise: {
|
||||||
|
type: 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gaussian noise, invalid amount of channels', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp({
|
||||||
|
create: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
channels: 5,
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 5,
|
||||||
|
sigma: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gaussian noise, invalid mean', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp({
|
||||||
|
create: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
channels: 1,
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: -1.5,
|
||||||
|
sigma: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gaussian noise, invalid sigma', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp({
|
||||||
|
create: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
channels: 1,
|
||||||
|
noise: {
|
||||||
|
type: 'gaussian',
|
||||||
|
mean: 0,
|
||||||
|
sigma: -1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user