mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add gravity support to crop #45
This commit is contained in:
parent
8ef0851a49
commit
5fe945fca8
@ -95,7 +95,7 @@ sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
|
||||
```
|
||||
|
||||
```javascript
|
||||
var transformer = sharp().resize(300, 200);
|
||||
var transformer = sharp().resize(300, 200).crop(sharp.gravity.north);
|
||||
readableStream.pipe(transformer).pipe(writableStream);
|
||||
// Read image data from readableStream, resize and write image data to writableStream
|
||||
```
|
||||
@ -183,10 +183,14 @@ Scale output to `width` x `height`. By default, the resized image is cropped to
|
||||
|
||||
`height` is the Number of pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
|
||||
#### crop()
|
||||
#### crop([gravity])
|
||||
|
||||
Crop the resized image to the exact size specified, the default behaviour.
|
||||
|
||||
`gravity`, if present, is an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
|
||||
|
||||
Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The default gravity is `center`/`centre`.
|
||||
|
||||
#### max()
|
||||
|
||||
Preserving aspect ratio, resize the image to the maximum width or height specified.
|
||||
|
@ -13,9 +13,9 @@
|
||||
'<!(node -e "require(\'nan\')")'
|
||||
],
|
||||
'cflags': ['-fexceptions', '-Wall', '-O3'],
|
||||
'cflags_cc': ['-fexceptions', '-Wall', '-O3'],
|
||||
'cflags_cc': ['-std=c++0x', '-fexceptions', '-Wall', '-O3'],
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS': ['-fexceptions', '-Wall', '-O3']
|
||||
'OTHER_CFLAGS': ['-std=c++11', '-fexceptions', '-Wall', '-O3']
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
14
index.js
14
index.js
@ -15,6 +15,7 @@ var Sharp = function(input) {
|
||||
width: -1,
|
||||
height: -1,
|
||||
canvas: 'c',
|
||||
gravity: 0,
|
||||
angle: 0,
|
||||
withoutEnlargement: false,
|
||||
sharpen: false,
|
||||
@ -72,8 +73,19 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
Sharp.prototype.crop = function() {
|
||||
// Crop this part of the resized image (Center/Centre, North, East, South, West)
|
||||
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
|
||||
|
||||
Sharp.prototype.crop = function(gravity) {
|
||||
this.options.canvas = 'c';
|
||||
if (typeof gravity !== 'undefined') {
|
||||
// Is this a supported gravity?
|
||||
if (!Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
||||
this.options.gravity = gravity;
|
||||
} else {
|
||||
throw new Error('Unsupported crop gravity ' + gravity);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
|
44
src/sharp.cc
44
src/sharp.cc
@ -3,6 +3,7 @@
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <tuple>
|
||||
#include <vips/vips.h>
|
||||
|
||||
#include "nan.h"
|
||||
@ -20,6 +21,7 @@ struct resize_baton {
|
||||
int width;
|
||||
int height;
|
||||
bool crop;
|
||||
int gravity;
|
||||
bool max;
|
||||
VipsExtend extend;
|
||||
bool sharpen;
|
||||
@ -36,6 +38,7 @@ struct resize_baton {
|
||||
buffer_in_len(0),
|
||||
buffer_out_len(0),
|
||||
crop(false),
|
||||
gravity(0),
|
||||
max(false),
|
||||
sharpen(false),
|
||||
progressive(false),
|
||||
@ -94,7 +97,8 @@ static void resize_error(resize_baton *baton, VipsImage *unref) {
|
||||
2. Use input image EXIF Orientation header (does not support mirroring)
|
||||
3. Otherwise default to zero, i.e. no rotation
|
||||
*/
|
||||
static VipsAngle calc_rotation(int const angle, VipsImage const *input) {
|
||||
static VipsAngle
|
||||
sharp_calc_rotation(int const angle, VipsImage const *input) {
|
||||
VipsAngle rotate = VIPS_ANGLE_0;
|
||||
if (angle == -1) {
|
||||
const char *exif;
|
||||
@ -119,6 +123,36 @@ static VipsAngle calc_rotation(int const angle, VipsImage const *input) {
|
||||
return rotate;
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the (left, top) coordinates of the output image
|
||||
within the input image, applying the given gravity.
|
||||
*/
|
||||
static std::tuple<int, int>
|
||||
sharp_calc_crop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity) {
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
switch (gravity) {
|
||||
case 1: // North
|
||||
left = (inWidth - outWidth + 1) / 2;
|
||||
break;
|
||||
case 2: // East
|
||||
left = inWidth - outWidth;
|
||||
top = (inHeight - outHeight + 1) / 2;
|
||||
break;
|
||||
case 3: // South
|
||||
left = (inWidth - outWidth + 1) / 2;
|
||||
top = inHeight - outHeight;
|
||||
break;
|
||||
case 4: // West
|
||||
top = (inHeight - outHeight + 1) / 2;
|
||||
break;
|
||||
default: // Centre
|
||||
left = (inWidth - outWidth + 1) / 2;
|
||||
top = (inHeight - outHeight + 1) / 2;
|
||||
}
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
class ResizeWorker : public NanAsyncWorker {
|
||||
public:
|
||||
ResizeWorker(NanCallback *callback, resize_baton *baton)
|
||||
@ -189,7 +223,7 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
int inputHeight = in->Ysize;
|
||||
|
||||
// Calculate angle of rotation, to be carried out later
|
||||
VipsAngle rotation = calc_rotation(baton->angle, in);
|
||||
VipsAngle rotation = sharp_calc_rotation(baton->angle, in);
|
||||
if (rotation == VIPS_ANGLE_90 || rotation == VIPS_ANGLE_270) {
|
||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
||||
int swap = inputWidth;
|
||||
@ -337,10 +371,11 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
if (rotated->Xsize != baton->width || rotated->Ysize != baton->height) {
|
||||
if (baton->crop || baton->max) {
|
||||
// Crop/max
|
||||
int left;
|
||||
int top;
|
||||
std::tie(left, top) = sharp_calc_crop(rotated->Xsize, rotated->Ysize, baton->width, baton->height, baton->gravity);
|
||||
int width = std::min(rotated->Xsize, baton->width);
|
||||
int height = std::min(rotated->Ysize, baton->height);
|
||||
int left = (rotated->Xsize - width + 1) / 2;
|
||||
int top = (rotated->Ysize - height + 1) / 2;
|
||||
if (vips_extract_area(rotated, &canvased, left, top, width, height, NULL)) {
|
||||
return resize_error(baton, rotated);
|
||||
}
|
||||
@ -507,6 +542,7 @@ NAN_METHOD(resize) {
|
||||
baton->max = true;
|
||||
}
|
||||
// Other options
|
||||
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
||||
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
||||
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
||||
|
@ -344,6 +344,60 @@ async.series([
|
||||
var pipeline = sharp().resize(320, 240);
|
||||
readable.pipe(pipeline).pipe(writable)
|
||||
},
|
||||
// Crop, gravity=north
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.north).toFile(path.join(fixturesPath, 'output.gravity-north.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=east
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.east).toFile(path.join(fixturesPath, 'output.gravity-east.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=south
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.south).toFile(path.join(fixturesPath, 'output.gravity-south.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=west
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.west).toFile(path.join(fixturesPath, 'output.gravity-west.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=center
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.center).toFile(path.join(fixturesPath, 'output.gravity-center.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=centre
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.centre).toFile(path.join(fixturesPath, 'output.gravity-centre.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Verify internal counters
|
||||
function(done) {
|
||||
var counters = sharp.counters();
|
||||
|
Loading…
x
Reference in New Issue
Block a user