mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Switch from custom trim op to vips_find_trim #914
This commit is contained in:
parent
11900945eb
commit
21fbe546b8
@ -19,6 +19,9 @@ Requires libvips v8.7.0.
|
|||||||
Per-operation `background` options added to `resize`, `extend` and `flatten` operations.
|
Per-operation `background` options added to `resize`, `extend` and `flatten` operations.
|
||||||
[#1392](https://github.com/lovell/sharp/issues/1392)
|
[#1392](https://github.com/lovell/sharp/issues/1392)
|
||||||
|
|
||||||
|
* Switch from custom trim operation to `vips_find_trim`.
|
||||||
|
[#914](https://github.com/lovell/sharp/issues/914)
|
||||||
|
|
||||||
* Drop Node 4 support.
|
* Drop Node 4 support.
|
||||||
[#1212](https://github.com/lovell/sharp/issues/1212)
|
[#1212](https://github.com/lovell/sharp/issues/1212)
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ const Sharp = function (input, options) {
|
|||||||
sharpenJagged: 2,
|
sharpenJagged: 2,
|
||||||
threshold: 0,
|
threshold: 0,
|
||||||
thresholdGrayscale: true,
|
thresholdGrayscale: true,
|
||||||
trimTolerance: 0,
|
trimThreshold: 0,
|
||||||
gamma: 0,
|
gamma: 0,
|
||||||
greyscale: false,
|
greyscale: false,
|
||||||
normalise: 0,
|
normalise: 0,
|
||||||
|
@ -359,18 +359,18 @@ function extract (options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel.
|
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
||||||
* @param {Number} [tolerance=10] value between 1 and 99 representing the percentage similarity.
|
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function trim (tolerance) {
|
function trim (threshold) {
|
||||||
if (!is.defined(tolerance)) {
|
if (!is.defined(threshold)) {
|
||||||
this.options.trimTolerance = 10;
|
this.options.trimThreshold = 10;
|
||||||
} else if (is.integer(tolerance) && is.inRange(tolerance, 1, 99)) {
|
} else if (is.number(threshold) && threshold > 0) {
|
||||||
this.options.trimTolerance = tolerance;
|
this.options.trimThreshold = threshold;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance);
|
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -324,55 +324,21 @@ namespace sharp {
|
|||||||
return image.boolean(imageR, boolean);
|
return image.boolean(imageR, boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
VImage Trim(VImage image, int const tolerance) {
|
/*
|
||||||
using sharp::MaximumImageAlpha;
|
Trim an image
|
||||||
// An equivalent of ImageMagick's -trim in C++ ... automatically remove
|
*/
|
||||||
// "boring" image edges.
|
VImage Trim(VImage image, int const threshold) {
|
||||||
|
// Top-left pixel provides the background colour
|
||||||
// We use .project to sum the rows and columns of a 0/255 mask image, the first
|
VImage background = image.extract_area(0, 0, 1, 1);
|
||||||
// non-zero row or column is the object edge. We make the mask image with an
|
if (HasAlpha(background)) {
|
||||||
// amount-different-from-background image plus a threshold.
|
background = background.flatten();
|
||||||
|
}
|
||||||
// find the value of the pixel at (0, 0) ... we will search for all pixels
|
int top, width, height;
|
||||||
// significantly different from this
|
int const left = image.find_trim(&top, &width, &height,
|
||||||
std::vector<double> background = image(0, 0);
|
VImage::option()->set("background", background(0, 0)));
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
double const 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");
|
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);
|
return image.extract_area(left, top, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Trim an image
|
Trim an image
|
||||||
*/
|
*/
|
||||||
VImage Trim(VImage image, int const tolerance);
|
VImage Trim(VImage image, int const threshold);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Linear adjustment (a * in + b)
|
* Linear adjustment (a * in + b)
|
||||||
|
@ -100,8 +100,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trim
|
// Trim
|
||||||
if (baton->trimTolerance != 0) {
|
if (baton->trimThreshold > 0.0) {
|
||||||
image = sharp::Trim(image, baton->trimTolerance);
|
image = sharp::Trim(image, baton->trimThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre extraction
|
// Pre extraction
|
||||||
@ -233,7 +233,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
if (
|
if (
|
||||||
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
|
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
|
||||||
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
|
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
|
||||||
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimTolerance == 0
|
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
|
||||||
) {
|
) {
|
||||||
if (xshrink >= 8 * shrink_on_load_factor) {
|
if (xshrink >= 8 * shrink_on_load_factor) {
|
||||||
xfactor = xfactor / 8;
|
xfactor = xfactor / 8;
|
||||||
@ -1183,7 +1183,7 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged");
|
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged");
|
||||||
baton->threshold = AttrTo<int32_t>(options, "threshold");
|
baton->threshold = AttrTo<int32_t>(options, "threshold");
|
||||||
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
|
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
|
||||||
baton->trimTolerance = AttrTo<int32_t>(options, "trimTolerance");
|
baton->trimThreshold = AttrTo<double>(options, "trimThreshold");
|
||||||
baton->gamma = AttrTo<double>(options, "gamma");
|
baton->gamma = AttrTo<double>(options, "gamma");
|
||||||
baton->linearA = AttrTo<double>(options, "linearA");
|
baton->linearA = AttrTo<double>(options, "linearA");
|
||||||
baton->linearB = AttrTo<double>(options, "linearB");
|
baton->linearB = AttrTo<double>(options, "linearB");
|
||||||
@ -1293,7 +1293,7 @@ NAN_METHOD(pipeline) {
|
|||||||
}
|
}
|
||||||
// Force random access for certain operations
|
// Force random access for certain operations
|
||||||
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
|
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
|
||||||
baton->trimTolerance != 0 || baton->normalise ||
|
baton->trimThreshold > 0.0 || baton->normalise ||
|
||||||
baton->position == 16 || baton->position == 17)) {
|
baton->position == 16 || baton->position == 17)) {
|
||||||
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ struct PipelineBaton {
|
|||||||
double sharpenJagged;
|
double sharpenJagged;
|
||||||
int threshold;
|
int threshold;
|
||||||
bool thresholdGrayscale;
|
bool thresholdGrayscale;
|
||||||
int trimTolerance;
|
double trimThreshold;
|
||||||
double linearA;
|
double linearA;
|
||||||
double linearB;
|
double linearB;
|
||||||
double gamma;
|
double gamma;
|
||||||
@ -176,7 +176,7 @@ struct PipelineBaton {
|
|||||||
sharpenJagged(2.0),
|
sharpenJagged(2.0),
|
||||||
threshold(0),
|
threshold(0),
|
||||||
thresholdGrayscale(true),
|
thresholdGrayscale(true),
|
||||||
trimTolerance(0),
|
trimThreshold(0.0),
|
||||||
linearA(1.0),
|
linearA(1.0),
|
||||||
linearB(0.0),
|
linearB(0.0),
|
||||||
gamma(0.0),
|
gamma(0.0),
|
||||||
|
@ -50,7 +50,7 @@ describe('Trim borders', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Invalid thresholds', function () {
|
describe('Invalid thresholds', function () {
|
||||||
[-1, 100, 'fail', {}].forEach(function (threshold) {
|
[-1, 'fail', {}].forEach(function (threshold) {
|
||||||
it(JSON.stringify(threshold), function () {
|
it(JSON.stringify(threshold), function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp().trim(threshold);
|
sharp().trim(threshold);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user