mirror of
https://github.com/lovell/sharp.git
synced 2025-07-13 04:20:12 +02:00
parent
673d8278b5
commit
b69627891d
18
index.js
18
index.js
@ -86,6 +86,7 @@ var Sharp = function(input, options) {
|
||||
sharpenJagged: 2,
|
||||
threshold: 0,
|
||||
thresholdGrayscale: true,
|
||||
trimTolerance: 0,
|
||||
gamma: 0,
|
||||
greyscale: false,
|
||||
normalize: 0,
|
||||
@ -564,6 +565,23 @@ Sharp.prototype.threshold = function(threshold, options) {
|
||||
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).
|
||||
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,
|
||||
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);
|
||||
|
||||
|
||||
|
@ -399,4 +399,56 @@ namespace sharp {
|
||||
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
|
||||
|
@ -87,6 +87,11 @@ namespace sharp {
|
||||
*/
|
||||
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
|
||||
|
||||
/*
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, int const tolerance);
|
||||
|
||||
} // namespace sharp
|
||||
|
||||
#endif // SRC_OPERATIONS_H_
|
||||
|
@ -56,6 +56,7 @@ using sharp::EntropyCrop;
|
||||
using sharp::TileCache;
|
||||
using sharp::Threshold;
|
||||
using sharp::Bandbool;
|
||||
using sharp::Trim;
|
||||
|
||||
using sharp::ImageType;
|
||||
using sharp::ImageTypeId;
|
||||
@ -207,6 +208,11 @@ class PipelineWorker : public AsyncWorker {
|
||||
RemoveExifOrientation(image);
|
||||
}
|
||||
|
||||
// Trim
|
||||
if(baton->trimTolerance != 0) {
|
||||
image = Trim(image, baton->trimTolerance);
|
||||
}
|
||||
|
||||
// Pre extraction
|
||||
if (baton->topOffsetPre != -1) {
|
||||
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->threshold = attrAs<int32_t>(options, "threshold");
|
||||
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->greyscale = attrAs<bool>(options, "greyscale");
|
||||
baton->normalize = attrAs<bool>(options, "normalize");
|
||||
|
@ -63,6 +63,7 @@ struct PipelineBaton {
|
||||
double sharpenJagged;
|
||||
int threshold;
|
||||
bool thresholdGrayscale;
|
||||
int trimTolerance;
|
||||
double gamma;
|
||||
bool greyscale;
|
||||
bool normalize;
|
||||
@ -127,6 +128,7 @@ struct PipelineBaton {
|
||||
sharpenJagged(2.0),
|
||||
threshold(0),
|
||||
thresholdGrayscale(true),
|
||||
trimTolerance(0),
|
||||
gamma(0.0),
|
||||
greyscale(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