Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linear transform #1024

Merged
merged 6 commits into from
Feb 4, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/api-operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [convolve](#convolve)
- [threshold](#threshold)
- [boolean](#boolean)
- [linear](#linear)

## rotate

Expand Down Expand Up @@ -321,3 +322,17 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
- Throws **[Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters

Returns **Sharp**

## linear

Apply the linear formula a \* input + b to the image (levels adjustment)

**Parameters**

- `a` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** multiplier (optional, default `1.0`)
- `b` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** offset (optional, default `0.0`)


- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters

Returns **Sharp**
2 changes: 2 additions & 0 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ const Sharp = function (input, options) {
tiffYres: 1.0,
tileSize: 256,
tileOverlap: 0,
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings
debuglog: debuglog,
// Function to notify of queue length changes
Expand Down
30 changes: 29 additions & 1 deletion lib/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,33 @@ function boolean (operand, operator, options) {
return this;
}

/**
* Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function linear (a, b) {
if (!is.defined(a)) {
this.options.linearA = 1.0;
} else if (is.number(a)) {
this.options.linearA = a;
} else {
throw new Error('Invalid linear transform multiplier ' + a);
}

if (!is.defined(b)) {
this.options.linearB = 0.0;
} else if (is.number(b)) {
this.options.linearB = b;
} else {
throw new Error('Invalid linear transform offset ' + b);
}

return this;
}

/**
* Decorate the Sharp prototype with operation-related functions.
* @private
Expand All @@ -427,7 +454,8 @@ module.exports = function (Sharp) {
normalize,
convolve,
threshold,
boolean
boolean,
linear
].forEach(function (f) {
Sharp.prototype[f.name] = f;
});
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"Guy Maliar <[email protected]>",
"Nicolas Coden <[email protected]>",
"Matt Parrish <[email protected]>",
"Marcel Bretschneider <[email protected]>"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a trailing comma is required here.

"Matthew McEachen <[email protected]>",
"Jarda Kotěšovec <[email protected]>",
"Kenric D'Souza <[email protected]>",
Expand Down
14 changes: 14 additions & 0 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,18 @@ namespace sharp {
return image.extract_area(left, top, width, height);
}

/*
* Calculate (a * in + b)
*/
VImage Linear(VImage image, double const a, double const b) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.linear(a, b).bandjoin(alpha);
} else {
return image.linear(a, b);
}
}
} // namespace sharp
5 changes: 5 additions & 0 deletions src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ namespace sharp {
*/
VImage Trim(VImage image, int const tolerance);

/*
* Linear adjustment (a * in + b)
*/
VImage Linear(VImage image, double const a, double const b);

} // namespace sharp

#endif // SRC_OPERATIONS_H_
7 changes: 7 additions & 0 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,11 @@ class PipelineWorker : public Nan::AsyncWorker {
image = sharp::Gamma(image, baton->gamma);
}

// Linear adjustment (a * in + b)
if (baton->linearA != 1.0 || baton->linearB != 0.0) {
image = sharp::Linear(image, baton->linearA, baton->linearB);
}

// Apply normalisation - stretch luminance to cover full dynamic range
if (baton->normalise) {
image = sharp::Normalise(image);
Expand Down Expand Up @@ -1185,6 +1190,8 @@ NAN_METHOD(pipeline) {
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
baton->trimTolerance = AttrTo<int32_t>(options, "trimTolerance");
baton->gamma = AttrTo<double>(options, "gamma");
baton->linearA = AttrTo<double>(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
Expand Down
4 changes: 4 additions & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ struct PipelineBaton {
int threshold;
bool thresholdGrayscale;
int trimTolerance;
double linearA;
double linearB;
double gamma;
bool greyscale;
bool normalise;
Expand Down Expand Up @@ -160,6 +162,8 @@ struct PipelineBaton {
threshold(0),
thresholdGrayscale(true),
trimTolerance(0),
linearA(1.0),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just spotted that these fields need to be initialised in the same order as they are declared to avoid a compiler warning.

../src/pipeline.h:83:10: warning: ‘PipelineBaton::gamma’ will be initialized after [-Wreorder]
   double gamma;
          ^
../src/pipeline.h:81:10: warning:   ‘double PipelineBaton::linearA’ [-Wreorder]
   double linearA;
          ^

Copy link
Contributor Author

@mbretschn mbretschn Nov 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to change this - but how do i actually see these compiler warnings. I use npm install to build a new release (unfortunately under windows) and there i can't see these warnings.
(I have to admit that I'm not programming cpp for so long.)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating - MSVC doesn't appear to warn about this but gcc and clang do.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linearB(0.0),
gamma(0.0),
greyscale(false),
normalise(false),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/low-contrast-linear.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/low-contrast-offset.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/low-contrast-slope.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions test/unit/linear.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

const sharp = require('../../');
const fixtures = require('../fixtures');

const assert = require('assert');

describe('Linear adjustment', function () {
const blackPoint = 70;
const whitePoint = 203;
const a = 255 / (whitePoint - blackPoint);
const b = -blackPoint * a;

it('applies linear levels adjustment w/o alpha ch', function (done) {
sharp(fixtures.inputJpgWithLowContrast)
.linear(a, b)
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('low-contrast-linear.jpg'), data, done);
});
});

it('applies slope level adjustment w/o alpha ch', function (done) {
sharp(fixtures.inputJpgWithLowContrast)
.linear(a)
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('low-contrast-slope.jpg'), data, done);
});
});

it('applies offset level adjustment w/o alpha ch', function (done) {
sharp(fixtures.inputJpgWithLowContrast)
.linear(null, b)
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('low-contrast-offset.jpg'), data, done);
});
});

it('applies linear levels adjustment w alpha ch', function (done) {
sharp(fixtures.inputPngOverlayLayer1)
.linear(a, b)
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('alpha-layer-1-fill-linear.png'), data, done);
});
});

it('applies slope level adjustment w alpha ch', function (done) {
sharp(fixtures.inputPngOverlayLayer1)
.linear(a)
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('alpha-layer-1-fill-slope.png'), data, done);
});
});

it('applies offset level adjustment w alpha ch', function (done) {
sharp(fixtures.inputPngOverlayLayer1)
.linear(null, b)
.toBuffer(function (err, data, info) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('alpha-layer-1-fill-offset.png'), data, done);
});
});

it('Invalid linear arguments', function () {
assert.throws(function () {
sharp(fixtures.inputPngOverlayLayer1)
.linear('foo');
});

assert.throws(function () {
sharp(fixtures.inputPngOverlayLayer1)
.linear(undefined, { 'bar': 'baz' });
});
});
});