mirror of
https://github.com/lovell/sharp.git
synced 2025-07-10 02:50:15 +02:00
Add experimental, entropy-based auto-crop
Remove deprecated extract API
This commit is contained in:
parent
38ddb3b866
commit
2034efcf55
25
docs/api.md
25
docs/api.md
@ -125,23 +125,34 @@ Scale output to `width` x `height`. By default, the resized image is cropped to
|
|||||||
|
|
||||||
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||||
|
|
||||||
#### crop([gravity])
|
#### crop([option])
|
||||||
|
|
||||||
Crop the resized image to the exact size specified, the default behaviour.
|
Crop the resized image to the exact size specified, the default behaviour.
|
||||||
|
|
||||||
`gravity`, if present, is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
|
`option`, if present, is an attribute of:
|
||||||
|
|
||||||
Possible values are `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` and `centre`.
|
* `sharp.gravity` e.g. `sharp.gravity.north`, to crop to an edge or corner, or
|
||||||
The default gravity is `center`/`centre`.
|
* `sharp.strategy` e.g. `sharp.strategy.entropy`, to crop dynamically.
|
||||||
|
|
||||||
|
Possible attributes of `sharp.gravity` are
|
||||||
|
`north`, `northeast`, `east`, `southeast`, `south`,
|
||||||
|
`southwest`, `west`, `northwest`, `center` and `centre`.
|
||||||
|
|
||||||
|
`sharp.strategy` currently contains only the experimental `entropy`,
|
||||||
|
which will retain the part of the image with the highest
|
||||||
|
[Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29) value.
|
||||||
|
|
||||||
|
The default option is a `center`/`centre` gravity.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var transformer = sharp()
|
var transformer = sharp()
|
||||||
.resize(300, 200)
|
.resize(200, 200)
|
||||||
.crop(sharp.gravity.north)
|
.crop(sharp.strategy.entropy)
|
||||||
.on('error', function(err) {
|
.on('error', function(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
// Read image data from readableStream, resize and write image data to writableStream
|
// Read image data from readableStream
|
||||||
|
// Write 200px square auto-cropped image data to writableStream
|
||||||
readableStream.pipe(transformer).pipe(writableStream);
|
readableStream.pipe(transformer).pipe(writableStream);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
[#239](https://github.com/lovell/sharp/issues/239)
|
[#239](https://github.com/lovell/sharp/issues/239)
|
||||||
[@chrisriley](https://github.com/chrisriley)
|
[@chrisriley](https://github.com/chrisriley)
|
||||||
|
|
||||||
|
* Add entropy-based strategy to determine crop region.
|
||||||
|
[#295](https://github.com/lovell/sharp/issues/295)
|
||||||
|
[@rightaway](https://github.com/rightaway)
|
||||||
|
|
||||||
* Expose density metadata; set density of images from vector input.
|
* Expose density metadata; set density of images from vector input.
|
||||||
[#338](https://github.com/lovell/sharp/issues/338)
|
[#338](https://github.com/lovell/sharp/issues/338)
|
||||||
[@lookfirst](https://github.com/lookfirst)
|
[@lookfirst](https://github.com/lookfirst)
|
||||||
|
65
index.js
65
index.js
@ -64,7 +64,7 @@ var Sharp = function(input, options) {
|
|||||||
width: -1,
|
width: -1,
|
||||||
height: -1,
|
height: -1,
|
||||||
canvas: 'crop',
|
canvas: 'crop',
|
||||||
gravity: 0,
|
crop: 0,
|
||||||
angle: 0,
|
angle: 0,
|
||||||
rotateBeforePreExtract: false,
|
rotateBeforePreExtract: false,
|
||||||
flip: false,
|
flip: false,
|
||||||
@ -231,48 +231,53 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Crop this part of the resized image (Center/Centre, North, East, South, West)
|
// Weighting to apply to image crop
|
||||||
module.exports.gravity = {
|
module.exports.gravity = {
|
||||||
'center': 0,
|
center: 0,
|
||||||
'centre': 0,
|
centre: 0,
|
||||||
'north': 1,
|
north: 1,
|
||||||
'east': 2,
|
east: 2,
|
||||||
'south': 3,
|
south: 3,
|
||||||
'west': 4,
|
west: 4,
|
||||||
'northeast': 5,
|
northeast: 5,
|
||||||
'southeast': 6,
|
southeast: 6,
|
||||||
'southwest': 7,
|
southwest: 7,
|
||||||
'northwest': 8
|
northwest: 8
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.crop = function(gravity) {
|
// Strategies for automagic behaviour
|
||||||
|
module.exports.strategy = {
|
||||||
|
entropy: 16
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
What part of the image should be retained when cropping?
|
||||||
|
*/
|
||||||
|
Sharp.prototype.crop = function(crop) {
|
||||||
this.options.canvas = 'crop';
|
this.options.canvas = 'crop';
|
||||||
if (!isDefined(gravity)) {
|
if (!isDefined(crop)) {
|
||||||
this.options.gravity = module.exports.gravity.center;
|
// Default
|
||||||
} else if (isInteger(gravity) && inRange(gravity, 0, 8)) {
|
this.options.crop = module.exports.gravity.center;
|
||||||
this.options.gravity = gravity;
|
} else if (isInteger(crop) && inRange(crop, 0, 8)) {
|
||||||
} else if (isString(gravity) && isInteger(module.exports.gravity[gravity])) {
|
// Gravity (numeric)
|
||||||
this.options.gravity = module.exports.gravity[gravity];
|
this.options.crop = crop;
|
||||||
|
} else if (isString(crop) && isInteger(module.exports.gravity[crop])) {
|
||||||
|
// Gravity (string)
|
||||||
|
this.options.crop = module.exports.gravity[crop];
|
||||||
|
} else if (isInteger(crop) && crop === module.exports.strategy.entropy) {
|
||||||
|
// Strategy
|
||||||
|
this.options.crop = crop;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unsupported crop gravity ' + gravity);
|
throw new Error('Unsupported crop ' + crop);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.extract = function(options) {
|
Sharp.prototype.extract = function(options) {
|
||||||
if (!options || typeof options !== 'object') {
|
|
||||||
// Legacy extract(top,left,width,height) syntax
|
|
||||||
options = {
|
|
||||||
left: arguments[1],
|
|
||||||
top: arguments[0],
|
|
||||||
width: arguments[2],
|
|
||||||
height: arguments[3]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
||||||
['left', 'top', 'width', 'height'].forEach(function (name) {
|
['left', 'top', 'width', 'height'].forEach(function (name) {
|
||||||
var value = options[name];
|
var value = options[name];
|
||||||
if (typeof value === 'number' && !Number.isNaN(value) && value % 1 === 0 && value >= 0) {
|
if (isInteger(value) && value >= 0) {
|
||||||
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
|
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Non-integer value for ' + name + ' of ' + value);
|
throw new Error('Non-integer value for ' + name + ' of ' + value);
|
||||||
|
@ -170,4 +170,82 @@ namespace sharp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate crop area based on image entropy
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight) {
|
||||||
|
int left = 0;
|
||||||
|
int top = 0;
|
||||||
|
int const inWidth = image.width();
|
||||||
|
int const inHeight = image.height();
|
||||||
|
if (inWidth > outWidth) {
|
||||||
|
// Reduce width by repeated removing slices from edge with lowest entropy
|
||||||
|
int width = inWidth;
|
||||||
|
double leftEntropy = 0.0;
|
||||||
|
double rightEntropy = 0.0;
|
||||||
|
// Max width of each slice
|
||||||
|
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
|
||||||
|
while (width > outWidth) {
|
||||||
|
// Width of current slice
|
||||||
|
int const slice = std::min(width - outWidth, maxSliceWidth);
|
||||||
|
if (leftEntropy == 0.0) {
|
||||||
|
// Update entropy of left slice
|
||||||
|
leftEntropy = Entropy(image.extract_area(left, 0, slice, inHeight));
|
||||||
|
}
|
||||||
|
if (rightEntropy == 0.0) {
|
||||||
|
// Update entropy of right slice
|
||||||
|
rightEntropy = Entropy(image.extract_area(width - slice - 1, 0, slice, inHeight));
|
||||||
|
}
|
||||||
|
// Keep slice with highest entropy
|
||||||
|
if (leftEntropy >= rightEntropy) {
|
||||||
|
// Discard right slice
|
||||||
|
rightEntropy = 0.0;
|
||||||
|
} else {
|
||||||
|
// Discard left slice
|
||||||
|
leftEntropy = 0.0;
|
||||||
|
left = left + slice;
|
||||||
|
}
|
||||||
|
width = width - slice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inHeight > outHeight) {
|
||||||
|
// Reduce height by repeated removing slices from edge with lowest entropy
|
||||||
|
int height = inHeight;
|
||||||
|
double topEntropy = 0.0;
|
||||||
|
double bottomEntropy = 0.0;
|
||||||
|
// Max height of each slice
|
||||||
|
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
|
||||||
|
while (height > outHeight) {
|
||||||
|
// Height of current slice
|
||||||
|
int const slice = std::min(height - outHeight, maxSliceHeight);
|
||||||
|
if (topEntropy == 0.0) {
|
||||||
|
// Update entropy of top slice
|
||||||
|
topEntropy = Entropy(image.extract_area(0, top, inWidth, slice));
|
||||||
|
}
|
||||||
|
if (bottomEntropy == 0.0) {
|
||||||
|
// Update entropy of bottom slice
|
||||||
|
bottomEntropy = Entropy(image.extract_area(0, height - slice - 1, inWidth, slice));
|
||||||
|
}
|
||||||
|
// Keep slice with highest entropy
|
||||||
|
if (topEntropy >= bottomEntropy) {
|
||||||
|
// Discard bottom slice
|
||||||
|
bottomEntropy = 0.0;
|
||||||
|
} else {
|
||||||
|
// Discard top slice
|
||||||
|
topEntropy = 0.0;
|
||||||
|
top = top + slice;
|
||||||
|
}
|
||||||
|
height = height - slice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::make_tuple(left, top);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the Shannon entropy for an image
|
||||||
|
*/
|
||||||
|
double Entropy(VImage image) {
|
||||||
|
return image.hist_find().hist_entropy();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
@ -33,6 +33,16 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged);
|
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate crop area based on image entropy
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> EntropyCrop(VImage image, int const outWidth, int const outHeight);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the Shannon entropy for an image
|
||||||
|
*/
|
||||||
|
double Entropy(VImage image);
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
#endif // SRC_OPERATIONS_H_
|
#endif // SRC_OPERATIONS_H_
|
||||||
|
@ -49,6 +49,7 @@ using sharp::Normalize;
|
|||||||
using sharp::Gamma;
|
using sharp::Gamma;
|
||||||
using sharp::Blur;
|
using sharp::Blur;
|
||||||
using sharp::Sharpen;
|
using sharp::Sharpen;
|
||||||
|
using sharp::EntropyCrop;
|
||||||
|
|
||||||
using sharp::ImageType;
|
using sharp::ImageType;
|
||||||
using sharp::ImageTypeId;
|
using sharp::ImageTypeId;
|
||||||
@ -506,9 +507,15 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Crop/max/min
|
// Crop/max/min
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
|
if (baton->crop < 9) {
|
||||||
|
// Gravity-based crop
|
||||||
std::tie(left, top) = CalculateCrop(
|
std::tie(left, top) = CalculateCrop(
|
||||||
image.width(), image.height(), baton->width, baton->height, baton->gravity
|
image.width(), image.height(), baton->width, baton->height, baton->crop
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Entropy-based crop
|
||||||
|
std::tie(left, top) = EntropyCrop(image, baton->width, baton->height);
|
||||||
|
}
|
||||||
int width = std::min(image.width(), baton->width);
|
int width = std::min(image.width(), baton->width);
|
||||||
int height = std::min(image.height(), baton->height);
|
int height = std::min(image.height(), baton->height);
|
||||||
image = image.extract_area(left, top, width, height);
|
image = image.extract_area(left, top, width, height);
|
||||||
@ -996,7 +1003,7 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
|
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
|
||||||
// Resize options
|
// Resize options
|
||||||
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
|
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
|
||||||
baton->gravity = attrAs<int32_t>(options, "gravity");
|
baton->crop = attrAs<int32_t>(options, "crop");
|
||||||
baton->interpolator = attrAsStr(options, "interpolator");
|
baton->interpolator = attrAsStr(options, "interpolator");
|
||||||
// Operators
|
// Operators
|
||||||
baton->flatten = attrAs<bool>(options, "flatten");
|
baton->flatten = attrAs<bool>(options, "flatten");
|
||||||
|
@ -45,7 +45,7 @@ struct PipelineBaton {
|
|||||||
int height;
|
int height;
|
||||||
int channels;
|
int channels;
|
||||||
Canvas canvas;
|
Canvas canvas;
|
||||||
int gravity;
|
int crop;
|
||||||
std::string interpolator;
|
std::string interpolator;
|
||||||
double background[4];
|
double background[4];
|
||||||
bool flatten;
|
bool flatten;
|
||||||
@ -99,7 +99,7 @@ struct PipelineBaton {
|
|||||||
topOffsetPost(-1),
|
topOffsetPost(-1),
|
||||||
channels(0),
|
channels(0),
|
||||||
canvas(Canvas::CROP),
|
canvas(Canvas::CROP),
|
||||||
gravity(0),
|
crop(0),
|
||||||
flatten(false),
|
flatten(false),
|
||||||
negate(false),
|
negate(false),
|
||||||
blurSigma(0.0),
|
blurSigma(0.0),
|
||||||
|
BIN
test/fixtures/expected/crop-entropy.jpg
vendored
Normal file
BIN
test/fixtures/expected/crop-entropy.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
BIN
test/fixtures/expected/crop-entropy.png
vendored
Normal file
BIN
test/fixtures/expected/crop-entropy.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
@ -5,9 +5,9 @@ var assert = require('assert');
|
|||||||
var sharp = require('../../index');
|
var sharp = require('../../index');
|
||||||
var fixtures = require('../fixtures');
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
describe('Crop gravities', function() {
|
describe('Crop', function() {
|
||||||
|
|
||||||
var testSettings = [
|
[
|
||||||
{
|
{
|
||||||
name: 'North',
|
name: 'North',
|
||||||
width: 320,
|
width: 320,
|
||||||
@ -50,6 +50,13 @@ describe('Crop gravities', function() {
|
|||||||
gravity: sharp.gravity.centre,
|
gravity: sharp.gravity.centre,
|
||||||
fixture: 'gravity-centre.jpg'
|
fixture: 'gravity-centre.jpg'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Default (centre)',
|
||||||
|
width: 80,
|
||||||
|
height: 320,
|
||||||
|
gravity: undefined,
|
||||||
|
fixture: 'gravity-centre.jpg'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Northeast',
|
name: 'Northeast',
|
||||||
width: 320,
|
width: 320,
|
||||||
@ -106,10 +113,8 @@ describe('Crop gravities', function() {
|
|||||||
gravity: sharp.gravity.northwest,
|
gravity: sharp.gravity.northwest,
|
||||||
fixture: 'gravity-west.jpg'
|
fixture: 'gravity-west.jpg'
|
||||||
}
|
}
|
||||||
];
|
].forEach(function(settings) {
|
||||||
|
it(settings.name + ' gravity', function(done) {
|
||||||
testSettings.forEach(function(settings) {
|
|
||||||
it(settings.name, function(done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(settings.width, settings.height)
|
.resize(settings.width, settings.height)
|
||||||
.crop(settings.gravity)
|
.crop(settings.gravity)
|
||||||
@ -122,7 +127,7 @@ describe('Crop gravities', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows specifying the gravity as a string', function(done) {
|
it('Allows specifying the gravity as a string', function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(80, 320)
|
.resize(80, 320)
|
||||||
.crop('east')
|
.crop('east')
|
||||||
@ -134,36 +139,57 @@ describe('Crop gravities', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Invalid number', function() {
|
it('Invalid values fail', function() {
|
||||||
assert.throws(function() {
|
assert.throws(function() {
|
||||||
sharp(fixtures.inputJpg).crop(9);
|
sharp().crop(9);
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp().crop(1.1);
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp().crop(-1);
|
||||||
|
});
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp().crop('zoinks');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Invalid string', function() {
|
it('Uses default value when none specified', function() {
|
||||||
assert.throws(function() {
|
|
||||||
sharp(fixtures.inputJpg).crop('yadda');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not throw if crop gravity is undefined', function() {
|
|
||||||
assert.doesNotThrow(function() {
|
assert.doesNotThrow(function() {
|
||||||
sharp(fixtures.inputJpg).crop();
|
sharp().crop();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults crop gravity to sharp.gravity.center', function(done) {
|
describe('Entropy-based strategy', function() {
|
||||||
var centerGravitySettings = testSettings.filter(function (settings) {
|
|
||||||
return settings.name === 'Center';
|
it('JPEG', function(done) {
|
||||||
})[0];
|
sharp(fixtures.inputJpgWithCmykProfile)
|
||||||
sharp(fixtures.inputJpg)
|
.resize(80, 320)
|
||||||
.resize(centerGravitySettings.width, centerGravitySettings.height)
|
.crop(sharp.strategy.entropy)
|
||||||
.crop()
|
|
||||||
.toBuffer(function(err, data, info) {
|
.toBuffer(function(err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(centerGravitySettings.width, info.width);
|
assert.strictEqual('jpeg', info.format);
|
||||||
assert.strictEqual(centerGravitySettings.height, info.height);
|
assert.strictEqual(3, info.channels);
|
||||||
fixtures.assertSimilar(fixtures.expected(centerGravitySettings.fixture), data, done);
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('crop-entropy.jpg'), data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.resize(320, 80)
|
||||||
|
.crop(sharp.strategy.entropy)
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('crop-entropy.png'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -6,29 +6,6 @@ var sharp = require('../../index');
|
|||||||
var fixtures = require('../fixtures');
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
describe('Partial image extraction', function() {
|
describe('Partial image extraction', function() {
|
||||||
describe('using the legacy extract(top,left,width,height) syntax', function () {
|
|
||||||
it('JPEG', function(done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.extract(2, 2, 20, 20)
|
|
||||||
.toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(20, info.width);
|
|
||||||
assert.strictEqual(20, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('extract.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('PNG', function(done) {
|
|
||||||
sharp(fixtures.inputPng)
|
|
||||||
.extract(300, 200, 400, 200)
|
|
||||||
.toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(400, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('extract.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('JPEG', function(done) {
|
it('JPEG', function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user