Skip to content

Commit

Permalink
Switch from custom trim op to vips_find_trim #914
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Oct 2, 2018
1 parent 1190094 commit 21fbe54
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 65 deletions.
3 changes: 3 additions & 0 deletions docs/changelog.md
Expand Up @@ -19,6 +19,9 @@ Requires libvips v8.7.0.
Per-operation `background` options added to `resize`, `extend` and `flatten` operations.
[#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.
[#1212](https://github.com/lovell/sharp/issues/1212)

Expand Down
2 changes: 1 addition & 1 deletion lib/constructor.js
Expand Up @@ -134,7 +134,7 @@ const Sharp = function (input, options) {
sharpenJagged: 2,
threshold: 0,
thresholdGrayscale: true,
trimTolerance: 0,
trimThreshold: 0,
gamma: 0,
greyscale: false,
normalise: 0,
Expand Down
16 changes: 8 additions & 8 deletions lib/resize.js
Expand Up @@ -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.
* @param {Number} [tolerance=10] value between 1 and 99 representing the percentage similarity.
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function trim (tolerance) {
if (!is.defined(tolerance)) {
this.options.trimTolerance = 10;
} else if (is.integer(tolerance) && is.inRange(tolerance, 1, 99)) {
this.options.trimTolerance = tolerance;
function trim (threshold) {
if (!is.defined(threshold)) {
this.options.trimThreshold = 10;
} else if (is.number(threshold) && threshold > 0) {
this.options.trimThreshold = threshold;
} else {
throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance);
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
}
return this;
}
Expand Down
60 changes: 13 additions & 47 deletions src/operations.cc
Expand Up @@ -324,55 +324,21 @@ namespace sharp {
return image.boolean(imageR, 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);

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) {
/*
Trim an image
*/
VImage Trim(VImage image, int const threshold) {
// Top-left pixel provides the background colour
VImage background = image.extract_area(0, 0, 1, 1);
if (HasAlpha(background)) {
background = background.flatten();
}
int top, width, height;
int const left = image.find_trim(&top, &width, &height,
VImage::option()->set("background", background(0, 0)));
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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/operations.h
Expand Up @@ -100,7 +100,7 @@ namespace sharp {
/*
Trim an image
*/
VImage Trim(VImage image, int const tolerance);
VImage Trim(VImage image, int const threshold);

/*
* Linear adjustment (a * in + b)
Expand Down
10 changes: 5 additions & 5 deletions src/pipeline.cc
Expand Up @@ -100,8 +100,8 @@ class PipelineWorker : public Nan::AsyncWorker {
}

// Trim
if (baton->trimTolerance != 0) {
image = sharp::Trim(image, baton->trimTolerance);
if (baton->trimThreshold > 0.0) {
image = sharp::Trim(image, baton->trimThreshold);
}

// Pre extraction
Expand Down Expand Up @@ -233,7 +233,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
(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) {
xfactor = xfactor / 8;
Expand Down Expand Up @@ -1183,7 +1183,7 @@ NAN_METHOD(pipeline) {
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged");
baton->threshold = AttrTo<int32_t>(options, "threshold");
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->linearA = AttrTo<double>(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB");
Expand Down Expand Up @@ -1293,7 +1293,7 @@ NAN_METHOD(pipeline) {
}
// Force random access for certain operations
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
baton->trimTolerance != 0 || baton->normalise ||
baton->trimThreshold > 0.0 || baton->normalise ||
baton->position == 16 || baton->position == 17)) {
baton->accessMethod = VIPS_ACCESS_RANDOM;
}
Expand Down
4 changes: 2 additions & 2 deletions src/pipeline.h
Expand Up @@ -81,7 +81,7 @@ struct PipelineBaton {
double sharpenJagged;
int threshold;
bool thresholdGrayscale;
int trimTolerance;
double trimThreshold;
double linearA;
double linearB;
double gamma;
Expand Down Expand Up @@ -176,7 +176,7 @@ struct PipelineBaton {
sharpenJagged(2.0),
threshold(0),
thresholdGrayscale(true),
trimTolerance(0),
trimThreshold(0.0),
linearA(1.0),
linearB(0.0),
gamma(0.0),
Expand Down
2 changes: 1 addition & 1 deletion test/unit/trim.js
Expand Up @@ -50,7 +50,7 @@ describe('Trim borders', function () {
});

describe('Invalid thresholds', function () {
[-1, 100, 'fail', {}].forEach(function (threshold) {
[-1, 'fail', {}].forEach(function (threshold) {
it(JSON.stringify(threshold), function () {
assert.throws(function () {
sharp().trim(threshold);
Expand Down

0 comments on commit 21fbe54

Please sign in to comment.