mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +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.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.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.
|
||||
|
||||
### Examples
|
||||
|
48
lib/input.js
48
lib/input.js
@ -137,22 +137,50 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
is.object(inputOptions.create) &&
|
||||
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
|
||||
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
|
||||
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
|
||||
is.defined(inputOptions.create.background)
|
||||
is.integer(inputOptions.create.channels)
|
||||
) {
|
||||
inputDescriptor.createWidth = inputOptions.create.width;
|
||||
inputDescriptor.createHeight = inputOptions.create.height;
|
||||
inputDescriptor.createChannels = inputOptions.create.channels;
|
||||
const background = color(inputOptions.create.background);
|
||||
inputDescriptor.createBackground = [
|
||||
background.red(),
|
||||
background.green(),
|
||||
background.blue(),
|
||||
Math.round(background.alpha() * 255)
|
||||
];
|
||||
// Noise
|
||||
if (is.defined(inputOptions.create.noise)) {
|
||||
if (!is.object(inputOptions.create.noise)) {
|
||||
throw new Error('Expected noise to be an object');
|
||||
}
|
||||
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;
|
||||
} 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)) {
|
||||
|
@ -73,7 +73,8 @@
|
||||
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
||||
"Christian Flintrup <chr@gigahost.dk>",
|
||||
"Manan Jadhav <manan@motionden.com>",
|
||||
"Leon Radley <leon@radley.se>"
|
||||
"Leon Radley <leon@radley.se>",
|
||||
"alza54 <alza54@thiocod.in>"
|
||||
],
|
||||
"scripts": {
|
||||
"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->createWidth = AttrAsUint32(input, "createWidth");
|
||||
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
|
||||
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
||||
@ -318,15 +324,35 @@ namespace sharp {
|
||||
} else {
|
||||
if (descriptor->createChannels > 0) {
|
||||
// Create new image
|
||||
std::vector<double> background = {
|
||||
descriptor->createBackground[0],
|
||||
descriptor->createBackground[1],
|
||||
descriptor->createBackground[2]
|
||||
};
|
||||
if (descriptor->createChannels == 4) {
|
||||
background.push_back(descriptor->createBackground[3]);
|
||||
if (descriptor->createNoiseType == "gaussian") {
|
||||
int const channels = descriptor->createChannels;
|
||||
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
|
||||
std::vector<VImage> bands = {};
|
||||
bands.reserve(channels);
|
||||
for (int _band = 0; _band < channels; _band++) {
|
||||
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;
|
||||
imageType = ImageType::RAW;
|
||||
} else {
|
||||
|
@ -64,6 +64,9 @@ namespace sharp {
|
||||
int createWidth;
|
||||
int createHeight;
|
||||
std::vector<double> createBackground;
|
||||
std::string createNoiseType;
|
||||
double createNoiseMean;
|
||||
double createNoiseSigma;
|
||||
|
||||
InputDescriptor():
|
||||
buffer(nullptr),
|
||||
@ -82,7 +85,9 @@ namespace sharp {
|
||||
createChannels(0),
|
||||
createWidth(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
|
||||
|
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