mirror of
https://github.com/lovell/sharp.git
synced 2025-07-13 12:20:13 +02:00
parent
673d8278b5
commit
b69627891d
18
index.js
18
index.js
@ -86,6 +86,7 @@ var Sharp = function(input, options) {
|
|||||||
sharpenJagged: 2,
|
sharpenJagged: 2,
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
thresholdGrayscale: true,
|
thresholdGrayscale: true,
|
||||||
|
trimTolerance: 0,
|
||||||
gamma: 0,
|
gamma: 0,
|
||||||
greyscale: false,
|
greyscale: false,
|
||||||
normalize: 0,
|
normalize: 0,
|
||||||
@ -564,6 +565,23 @@ Sharp.prototype.threshold = function(threshold, options) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Automatically remove "boring" image edges.
|
||||||
|
tolerance - if present, is a percentaged tolerance level between 0 and 100 to trim away similar color values
|
||||||
|
Defaulting to 10 when no tolerance is given.
|
||||||
|
*/
|
||||||
|
Sharp.prototype.trim = function(tolerance) {
|
||||||
|
if (typeof tolerance === 'undefined') {
|
||||||
|
this.options.trimTolerance = 10;
|
||||||
|
} else if (isInteger(tolerance) && inRange(tolerance, 1, 99)) {
|
||||||
|
this.options.trimTolerance = tolerance;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
||||||
Improves brightness of resized image in non-linear colour spaces.
|
Improves brightness of resized image in non-linear colour spaces.
|
||||||
|
@ -115,6 +115,10 @@ namespace sharp {
|
|||||||
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||||
int const outWidth, int const outHeight, int const x, int const y);
|
int const outWidth, int const outHeight, int const x, int const y);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return the image alpha maximum. Useful for combining alpha bands. scRGB
|
||||||
|
images are 0 - 1 for image data, but the alpha is 0 - 255.
|
||||||
|
*/
|
||||||
int MaximumImageAlpha(VipsInterpretation interpretation);
|
int MaximumImageAlpha(VipsInterpretation interpretation);
|
||||||
|
|
||||||
|
|
||||||
|
@ -399,4 +399,56 @@ namespace sharp {
|
|||||||
return image.bandbool(boolean);
|
return image.bandbool(boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VImage Trim(VImage image, int const tolerance) {
|
||||||
|
using sharp::MaximumImageAlpha;
|
||||||
|
// An equivalent of ImageMagick's -trim in C++ ... automatically remove
|
||||||
|
// "boring" image edges.
|
||||||
|
|
||||||
|
// We use .project to sum the rows and columns of a 0/255 mask image, the first
|
||||||
|
// non-zero row or column is the object edge. We make the mask image with an
|
||||||
|
// amount-different-from-background image plus a threshold.
|
||||||
|
|
||||||
|
// find the value of the pixel at (0, 0) ... we will search for all pixels
|
||||||
|
// significantly different from this
|
||||||
|
std::vector<double> background = image(0, 0);
|
||||||
|
|
||||||
|
int max = MaximumImageAlpha(image.interpretation());
|
||||||
|
|
||||||
|
// we need to smooth the image, subtract the background from every pixel, take
|
||||||
|
// the absolute value of the difference, then threshold
|
||||||
|
VImage mask = (image.median(3) - background).abs() > (max * tolerance / 100);
|
||||||
|
|
||||||
|
// sum mask rows and columns, then search for the first non-zero sum in each
|
||||||
|
// direction
|
||||||
|
VImage rows;
|
||||||
|
VImage columns = mask.project(&rows);
|
||||||
|
|
||||||
|
VImage profileLeftV;
|
||||||
|
VImage profileLeftH = columns.profile(&profileLeftV);
|
||||||
|
|
||||||
|
VImage profileRightV;
|
||||||
|
VImage profileRightH = columns.fliphor().profile(&profileRightV);
|
||||||
|
|
||||||
|
VImage profileTopV;
|
||||||
|
VImage profileTopH = rows.profile(&profileTopV);
|
||||||
|
|
||||||
|
VImage profileBottomV;
|
||||||
|
VImage profileBottomH = rows.flipver().profile(&profileBottomV);
|
||||||
|
|
||||||
|
int left = static_cast<int>(floor(profileLeftV.min()));
|
||||||
|
int right = columns.width() - static_cast<int>(floor(profileRightV.min()));
|
||||||
|
int top = static_cast<int>(floor(profileTopH.min()));
|
||||||
|
int bottom = rows.height() - static_cast<int>(floor(profileBottomH.min()));
|
||||||
|
|
||||||
|
int width = right - left;
|
||||||
|
int height = bottom - top;
|
||||||
|
|
||||||
|
if(width <= 0 || height <= 0) {
|
||||||
|
throw VError("Unexpected error while trimming. Try to lower the tolerance");
|
||||||
|
}
|
||||||
|
|
||||||
|
// and now crop the original image
|
||||||
|
return image.extract_area(left, top, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
@ -87,6 +87,11 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
|
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Trim an image
|
||||||
|
*/
|
||||||
|
VImage Trim(VImage image, int const tolerance);
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
#endif // SRC_OPERATIONS_H_
|
#endif // SRC_OPERATIONS_H_
|
||||||
|
@ -56,6 +56,7 @@ using sharp::EntropyCrop;
|
|||||||
using sharp::TileCache;
|
using sharp::TileCache;
|
||||||
using sharp::Threshold;
|
using sharp::Threshold;
|
||||||
using sharp::Bandbool;
|
using sharp::Bandbool;
|
||||||
|
using sharp::Trim;
|
||||||
|
|
||||||
using sharp::ImageType;
|
using sharp::ImageType;
|
||||||
using sharp::ImageTypeId;
|
using sharp::ImageTypeId;
|
||||||
@ -207,6 +208,11 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
RemoveExifOrientation(image);
|
RemoveExifOrientation(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim
|
||||||
|
if(baton->trimTolerance != 0) {
|
||||||
|
image = Trim(image, baton->trimTolerance);
|
||||||
|
}
|
||||||
|
|
||||||
// Pre extraction
|
// Pre extraction
|
||||||
if (baton->topOffsetPre != -1) {
|
if (baton->topOffsetPre != -1) {
|
||||||
image = image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
|
image = image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
|
||||||
@ -1145,6 +1151,10 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->sharpenJagged = attrAs<double>(options, "sharpenJagged");
|
baton->sharpenJagged = attrAs<double>(options, "sharpenJagged");
|
||||||
baton->threshold = attrAs<int32_t>(options, "threshold");
|
baton->threshold = attrAs<int32_t>(options, "threshold");
|
||||||
baton->thresholdGrayscale = attrAs<bool>(options, "thresholdGrayscale");
|
baton->thresholdGrayscale = attrAs<bool>(options, "thresholdGrayscale");
|
||||||
|
baton->trimTolerance = attrAs<int32_t>(options, "trimTolerance");
|
||||||
|
if(baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && baton->trimTolerance != 0) {
|
||||||
|
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
||||||
|
}
|
||||||
baton->gamma = attrAs<double>(options, "gamma");
|
baton->gamma = attrAs<double>(options, "gamma");
|
||||||
baton->greyscale = attrAs<bool>(options, "greyscale");
|
baton->greyscale = attrAs<bool>(options, "greyscale");
|
||||||
baton->normalize = attrAs<bool>(options, "normalize");
|
baton->normalize = attrAs<bool>(options, "normalize");
|
||||||
|
@ -63,6 +63,7 @@ struct PipelineBaton {
|
|||||||
double sharpenJagged;
|
double sharpenJagged;
|
||||||
int threshold;
|
int threshold;
|
||||||
bool thresholdGrayscale;
|
bool thresholdGrayscale;
|
||||||
|
int trimTolerance;
|
||||||
double gamma;
|
double gamma;
|
||||||
bool greyscale;
|
bool greyscale;
|
||||||
bool normalize;
|
bool normalize;
|
||||||
@ -127,6 +128,7 @@ struct PipelineBaton {
|
|||||||
sharpenJagged(2.0),
|
sharpenJagged(2.0),
|
||||||
threshold(0),
|
threshold(0),
|
||||||
thresholdGrayscale(true),
|
thresholdGrayscale(true),
|
||||||
|
trimTolerance(0),
|
||||||
gamma(0.0),
|
gamma(0.0),
|
||||||
greyscale(false),
|
greyscale(false),
|
||||||
normalize(false),
|
normalize(false),
|
||||||
|
BIN
test/fixtures/expected/alpha-layer-1-fill-trim-resize.png
vendored
Normal file
BIN
test/fixtures/expected/alpha-layer-1-fill-trim-resize.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
test/fixtures/expected/trim-16bit-rgba.png
vendored
Normal file
BIN
test/fixtures/expected/trim-16bit-rgba.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
39
test/unit/trim.js
Normal file
39
test/unit/trim.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('Trim borders', function() {
|
||||||
|
|
||||||
|
it('Threshold default', function(done) {
|
||||||
|
var expected = fixtures.expected('alpha-layer-1-fill-trim-resize.png');
|
||||||
|
sharp(fixtures.inputPngOverlayLayer1)
|
||||||
|
.resize(450, 322)
|
||||||
|
.trim()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(450, info.width);
|
||||||
|
assert.strictEqual(322, info.height);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('16-bit PNG with alpha channel', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency16bit)
|
||||||
|
.resize(32, 32)
|
||||||
|
.trim()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(32, info.width);
|
||||||
|
assert.strictEqual(32, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('trim-16bit-rgba.png'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user