mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Options for trim op must be an Object, add lineArt #2363
This commit is contained in:
parent
2e7c60675b
commit
0bd1715f36
@ -251,7 +251,7 @@ sharp(input)
|
||||
|
||||
|
||||
## trim
|
||||
> trim(trim) ⇒ <code>Sharp</code>
|
||||
> trim([options]) ⇒ <code>Sharp</code>
|
||||
|
||||
Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
||||
|
||||
@ -259,8 +259,7 @@ Images with an alpha channel will use the combined bounding box of alpha and non
|
||||
|
||||
If the result of this operation would trim an image to nothing then no change is made.
|
||||
|
||||
The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||
will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
|
||||
|
||||
**Throws**:
|
||||
@ -270,46 +269,44 @@ will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| trim | <code>string</code> \| <code>number</code> \| <code>Object</code> | | the specific background colour to trim, the threshold for doing so or an Object with both. |
|
||||
| [trim.background] | <code>string</code> \| <code>Object</code> | <code>"'top-left pixel'"</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. |
|
||||
| [trim.threshold] | <code>number</code> | <code>10</code> | the allowed difference from the above colour, a positive number. |
|
||||
| [options] | <code>Object</code> | | |
|
||||
| [options.background] | <code>string</code> \| <code>Object</code> | <code>"'top-left pixel'"</code> | Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel. |
|
||||
| [options.threshold] | <code>number</code> | <code>10</code> | Allowed difference from the above colour, a positive number. |
|
||||
| [options.lineArt] | <code>boolean</code> | <code>false</code> | Does the input more closely resemble line art (e.g. vector) rather than being photographic? |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
// Trim pixels with a colour similar to that of the top-left pixel.
|
||||
sharp(input)
|
||||
await sharp(input)
|
||||
.trim()
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
.toFile(output);
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
// Trim pixels with the exact same colour as that of the top-left pixel.
|
||||
sharp(input)
|
||||
.trim(0)
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
await sharp(input)
|
||||
.trim({
|
||||
threshold: 0
|
||||
})
|
||||
.toFile(output);
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
// Trim only pixels with a similar colour to red.
|
||||
sharp(input)
|
||||
.trim("#FF0000")
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
// Assume input is line art and trim only pixels with a similar colour to red.
|
||||
const output = await sharp(input)
|
||||
.trim({
|
||||
background: "#FF0000",
|
||||
lineArt: true
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
// Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
||||
sharp(input)
|
||||
const output = await sharp(input)
|
||||
.trim({
|
||||
background: "yellow",
|
||||
threshold: 42,
|
||||
})
|
||||
.toFile(output, function(err, info) {
|
||||
...
|
||||
});
|
||||
.toBuffer();
|
||||
```
|
@ -14,6 +14,9 @@ Requires libvips v8.15.0
|
||||
|
||||
* Remove `sharp.vendor`.
|
||||
|
||||
* Options for `trim` operation must be an Object, add new `lineArt` option.
|
||||
[#2363](https://github.com/lovell/sharp/issues/2363)
|
||||
|
||||
* Ensure all `Error` objects contain a `stack` property.
|
||||
[#3653](https://github.com/lovell/sharp/issues/3653)
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -230,7 +230,8 @@ const Sharp = function (input, options) {
|
||||
threshold: 0,
|
||||
thresholdGrayscale: true,
|
||||
trimBackground: [],
|
||||
trimThreshold: 0,
|
||||
trimThreshold: -1,
|
||||
trimLineArt: false,
|
||||
gamma: 0,
|
||||
gammaOut: 0,
|
||||
greyscale: false,
|
||||
|
10
lib/index.d.ts
vendored
10
lib/index.d.ts
vendored
@ -847,11 +847,11 @@ declare namespace sharp {
|
||||
* Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
||||
* Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
|
||||
* The info response Object will contain trimOffsetLeft and trimOffsetTop properties.
|
||||
* @param trim The specific background colour to trim, the threshold for doing so or an Object with both.
|
||||
* @param options trim options
|
||||
* @throws {Error} Invalid parameters
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
trim(trim?: string | number | TrimOptions): Sharp;
|
||||
trim(options?: TrimOptions): Sharp;
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@ -1342,10 +1342,12 @@ declare namespace sharp {
|
||||
}
|
||||
|
||||
interface TrimOptions {
|
||||
/** background colour, parsed by the color module, defaults to that of the top-left pixel. (optional) */
|
||||
/** Background colour, parsed by the color module, defaults to that of the top-left pixel. (optional) */
|
||||
background?: Color | undefined;
|
||||
/** the allowed difference from the above colour, a positive number. (optional, default `10`) */
|
||||
/** Allowed difference from the above colour, a positive number. (optional, default 10) */
|
||||
threshold?: number | undefined;
|
||||
/** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */
|
||||
lineArt?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface RawOptions {
|
||||
|
@ -494,70 +494,67 @@ function extract (options) {
|
||||
*
|
||||
* If the result of this operation would trim an image to nothing then no change is made.
|
||||
*
|
||||
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
||||
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
||||
*
|
||||
* @example
|
||||
* // Trim pixels with a colour similar to that of the top-left pixel.
|
||||
* sharp(input)
|
||||
* await sharp(input)
|
||||
* .trim()
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* .toFile(output);
|
||||
*
|
||||
* @example
|
||||
* // Trim pixels with the exact same colour as that of the top-left pixel.
|
||||
* sharp(input)
|
||||
* .trim(0)
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* await sharp(input)
|
||||
* .trim({
|
||||
* threshold: 0
|
||||
* })
|
||||
* .toFile(output);
|
||||
*
|
||||
* @example
|
||||
* // Trim only pixels with a similar colour to red.
|
||||
* sharp(input)
|
||||
* .trim("#FF0000")
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* // Assume input is line art and trim only pixels with a similar colour to red.
|
||||
* const output = await sharp(input)
|
||||
* .trim({
|
||||
* background: "#FF0000",
|
||||
* lineArt: true
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
||||
* sharp(input)
|
||||
* const output = await sharp(input)
|
||||
* .trim({
|
||||
* background: "yellow",
|
||||
* threshold: 42,
|
||||
* })
|
||||
* .toFile(output, function(err, info) {
|
||||
* ...
|
||||
* });
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {string|number|Object} trim - the specific background colour to trim, the threshold for doing so or an Object with both.
|
||||
* @param {string|Object} [trim.background='top-left pixel'] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
|
||||
* @param {number} [trim.threshold=10] - the allowed difference from the above colour, a positive number.
|
||||
* @param {Object} [options]
|
||||
* @param {string|Object} [options.background='top-left pixel'] - Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
|
||||
* @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number.
|
||||
* @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic?
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function trim (trim) {
|
||||
if (!is.defined(trim)) {
|
||||
function trim (options) {
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.string(trim)) {
|
||||
this._setBackgroundColourOption('trimBackground', trim);
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.number(trim)) {
|
||||
if (trim >= 0) {
|
||||
this.options.trimThreshold = trim;
|
||||
} else {
|
||||
throw is.invalidParameterError('threshold', 'positive number', trim);
|
||||
if (is.defined(options)) {
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.background)) {
|
||||
this._setBackgroundColourOption('trimBackground', options.background);
|
||||
}
|
||||
} else if (is.object(trim)) {
|
||||
this._setBackgroundColourOption('trimBackground', trim.background);
|
||||
if (!is.defined(trim.threshold)) {
|
||||
this.options.trimThreshold = 10;
|
||||
} else if (is.number(trim.threshold) && trim.threshold >= 0) {
|
||||
this.options.trimThreshold = trim.threshold;
|
||||
if (is.defined(options.threshold)) {
|
||||
if (is.number(options.threshold) && options.threshold >= 0) {
|
||||
this.options.trimThreshold = options.threshold;
|
||||
} else {
|
||||
throw is.invalidParameterError('threshold', 'positive number', trim);
|
||||
throw is.invalidParameterError('threshold', 'positive number', options.threshold);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.lineArt)) {
|
||||
this._setBooleanOption('trimLineArt', options.lineArt);
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('trim', 'string, number or object', trim);
|
||||
throw is.invalidParameterError('trim', 'object', options);
|
||||
}
|
||||
}
|
||||
if (isRotationExpected(this.options)) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
|
@ -265,7 +265,7 @@ namespace sharp {
|
||||
/*
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, std::vector<double> background, double threshold) {
|
||||
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt) {
|
||||
if (image.width() < 3 && image.height() < 3) {
|
||||
throw VError("Image to trim must be at least 3x3 pixels");
|
||||
}
|
||||
@ -287,6 +287,7 @@ namespace sharp {
|
||||
int left, top, width, height;
|
||||
left = image.find_trim(&top, &width, &height, VImage::option()
|
||||
->set("background", background)
|
||||
->set("line_art", lineArt)
|
||||
->set("threshold", threshold));
|
||||
if (HasAlpha(image)) {
|
||||
// Search alpha channel (A)
|
||||
@ -294,6 +295,7 @@ namespace sharp {
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
|
||||
->set("background", backgroundAlpha)
|
||||
->set("line_art", lineArt)
|
||||
->set("threshold", threshold));
|
||||
if (widthA > 0 && heightA > 0) {
|
||||
if (width > 0 && height > 0) {
|
||||
|
@ -79,7 +79,7 @@ namespace sharp {
|
||||
/*
|
||||
Trim an image
|
||||
*/
|
||||
VImage Trim(VImage image, std::vector<double> background, double const threshold);
|
||||
VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt);
|
||||
|
||||
/*
|
||||
* Linear adjustment (a * in + b)
|
||||
|
@ -126,10 +126,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Trim
|
||||
if (baton->trimThreshold > 0.0) {
|
||||
if (baton->trimThreshold >= 0.0) {
|
||||
MultiPageUnsupported(nPages, "Trim");
|
||||
image = sharp::StaySequential(image, access);
|
||||
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold);
|
||||
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
|
||||
baton->trimOffsetLeft = image.xoffset();
|
||||
baton->trimOffsetTop = image.yoffset();
|
||||
}
|
||||
@ -182,7 +182,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// - trimming or pre-resize extract isn't required;
|
||||
// - input colourspace is not specified;
|
||||
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
||||
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
|
||||
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
|
||||
baton->colourspaceInput == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
|
||||
|
||||
if (shouldPreShrink) {
|
||||
@ -1248,11 +1248,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
info.Set("attentionX", static_cast<int32_t>(baton->attentionX));
|
||||
info.Set("attentionY", static_cast<int32_t>(baton->attentionY));
|
||||
}
|
||||
if (baton->trimThreshold > 0.0) {
|
||||
if (baton->trimThreshold >= 0.0) {
|
||||
info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
|
||||
info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
|
||||
}
|
||||
|
||||
if (baton->input->textAutofitDpi) {
|
||||
info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
|
||||
}
|
||||
@ -1519,6 +1518,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
|
||||
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
|
||||
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
||||
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
|
||||
baton->gamma = sharp::AttrAsDouble(options, "gamma");
|
||||
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
|
||||
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
|
||||
|
@ -92,6 +92,7 @@ struct PipelineBaton {
|
||||
bool thresholdGrayscale;
|
||||
std::vector<double> trimBackground;
|
||||
double trimThreshold;
|
||||
bool trimLineArt;
|
||||
int trimOffsetLeft;
|
||||
int trimOffsetTop;
|
||||
std::vector<double> linearA;
|
||||
@ -260,7 +261,8 @@ struct PipelineBaton {
|
||||
threshold(0),
|
||||
thresholdGrayscale(true),
|
||||
trimBackground{},
|
||||
trimThreshold(0.0),
|
||||
trimThreshold(-1.0),
|
||||
trimLineArt(false),
|
||||
trimOffsetLeft(0),
|
||||
trimOffsetTop(0),
|
||||
linearA{},
|
||||
|
@ -572,8 +572,8 @@ const nohalo: string = sharp.interpolators.nohalo;
|
||||
const vertexSplitQuadraticBasisSpline: string = sharp.interpolators.vertexSplitQuadraticBasisSpline;
|
||||
|
||||
// Triming
|
||||
sharp(input).trim('#000').toBuffer();
|
||||
sharp(input).trim(10).toBuffer();
|
||||
sharp(input).trim({ background: '#000' }).toBuffer();
|
||||
sharp(input).trim({ threshold: 10, lineArt: true }).toBuffer();
|
||||
sharp(input).trim({ background: '#bf1942', threshold: 30 }).toBuffer();
|
||||
|
||||
// Text input
|
||||
|
@ -46,7 +46,9 @@ describe('Trim borders', function () {
|
||||
it('16-bit PNG with alpha channel', function (done) {
|
||||
sharp(fixtures.inputPngWithTransparency16bit)
|
||||
.resize(32, 32)
|
||||
.trim(20)
|
||||
.trim({
|
||||
threshold: 20
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
@ -96,7 +98,9 @@ describe('Trim borders', function () {
|
||||
.then(rotated30 =>
|
||||
sharp(rotated30)
|
||||
.rotate(-30)
|
||||
.trim(128)
|
||||
.trim({
|
||||
threshold: 128
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(({ info }) => {
|
||||
assert.strictEqual(20, info.width);
|
||||
@ -198,49 +202,26 @@ describe('Trim borders', function () {
|
||||
assert.strictEqual(trimOffsetLeft, 0);
|
||||
});
|
||||
|
||||
describe('Valid parameters', function () {
|
||||
const expected = fixtures.expected('alpha-layer-1-fill-trim-resize.png');
|
||||
Object.entries({
|
||||
'Background and threshold default': undefined,
|
||||
'Background string': '#00000000',
|
||||
'Background option': {
|
||||
background: '#00000000'
|
||||
},
|
||||
'Threshold number': 10,
|
||||
'Threshold option': {
|
||||
threshold: 10
|
||||
}
|
||||
}).forEach(function ([description, parameter]) {
|
||||
it(description, function (done) {
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.resize(450, 322)
|
||||
.trim(parameter)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(450, info.width);
|
||||
assert.strictEqual(322, info.height);
|
||||
assert.strictEqual(-204, info.trimOffsetLeft);
|
||||
assert.strictEqual(0, info.trimOffsetTop);
|
||||
fixtures.assertSimilar(expected, data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('Works with line-art', async () => {
|
||||
const { info } = await sharp(fixtures.inputJpgOverlayLayer2)
|
||||
.trim({ lineArt: true })
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
assert.strictEqual(info.trimOffsetTop, -552);
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function () {
|
||||
Object.entries({
|
||||
'Invalid background string': 'fail',
|
||||
'Invalid string': 'fail',
|
||||
'Invalid background option': {
|
||||
background: 'fail'
|
||||
},
|
||||
|
||||
'Negative threshold number': -1,
|
||||
'Negative threshold option': {
|
||||
threshold: -1
|
||||
},
|
||||
|
||||
Boolean: false
|
||||
'Invalid lineArt': {
|
||||
lineArt: 'fail'
|
||||
}
|
||||
}).forEach(function ([description, parameter]) {
|
||||
it(description, function () {
|
||||
assert.throws(function () {
|
||||
@ -253,7 +234,9 @@ describe('Trim borders', function () {
|
||||
describe('Specific background colour', function () {
|
||||
it('Doesn\'t trim at all', async () => {
|
||||
const { info } = await sharp(fixtures.inputPngTrimSpecificColour)
|
||||
.trim('yellow')
|
||||
.trim({
|
||||
background: 'yellow'
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
|
||||
@ -265,7 +248,9 @@ describe('Trim borders', function () {
|
||||
|
||||
it('Only trims the bottom', async () => {
|
||||
const { info } = await sharp(fixtures.inputPngTrimSpecificColour)
|
||||
.trim('#21468B')
|
||||
.trim({
|
||||
background: '#21468B'
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
|
||||
@ -277,7 +262,9 @@ describe('Trim borders', function () {
|
||||
|
||||
it('Only trims the bottom, in 16-bit', async () => {
|
||||
const { info } = await sharp(fixtures.inputPngTrimSpecificColour16bit)
|
||||
.trim('#21468B')
|
||||
.trim({
|
||||
background: '#21468B'
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
|
||||
@ -289,7 +276,9 @@ describe('Trim borders', function () {
|
||||
|
||||
it('Only trims the bottom, including alpha', async () => {
|
||||
const { info } = await sharp(fixtures.inputPngTrimSpecificColourIncludeAlpha)
|
||||
.trim('#21468B80')
|
||||
.trim({
|
||||
background: '#21468B80'
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
const { width, height, trimOffsetTop, trimOffsetLeft } = info;
|
||||
|
Loading…
x
Reference in New Issue
Block a user