Compare commits

...

11 Commits

Author SHA1 Message Date
Lovell Fuller
9fa516e849 Release v0.31.2 2022-11-04 09:44:37 +00:00
Lovell Fuller
12f472126d CI: Only pin Python version on x64 macOS and Windows
See commit 18be09f
2022-11-03 14:49:12 +00:00
Lovell Fuller
18be09f1d7 CI: Pin Python to 3.10
Python 3.11 removes support for opening files in
'universal newline' mode (e.g. 'rU'), however older
versions of node-gyp such as v6 still use it.
2022-11-03 14:40:16 +00:00
Lovell Fuller
b3c3290f90 Upgrade to libvips v8.13.3 2022-11-03 14:09:23 +00:00
Lovell Fuller
123f95c85a Bump devDeps 2022-11-03 12:50:58 +00:00
Lovell Fuller
5b0fba4c01 Ensure auto-rotate always works without resize #3422 2022-11-02 13:59:34 +00:00
Lovell Fuller
37f7ccfff4 CI: upgrade to checkout v3 2022-10-17 16:05:30 +01:00
Lovell Fuller
51811d06e2 Bump deps 2022-10-17 16:05:03 +01:00
Lovell Fuller
181731f8f4 Tests: increase timeout to 30s
Font discovery appears to be slooow on Windows
2022-10-17 15:54:43 +01:00
Gino Emiliozzi
ae79d26ead Docs: help clarify 'fit' is option name, not value (#3410) 2022-10-17 15:26:59 +01:00
Lovell Fuller
eacb8337fa Ensure manual flip, rotate, resize op order #3391 2022-10-01 11:55:29 +01:00
10 changed files with 139 additions and 66 deletions

View File

@@ -22,13 +22,13 @@ jobs:
run:
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
steps:
- name: Dependencies
- name: Dependencies (Node.js)
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install
run: npm install --build-from-source --unsafe-perm
- name: Test

View File

@@ -78,14 +78,19 @@ jobs:
- name: Dependencies (Linux musl)
if: contains(matrix.container, 'alpine')
run: apk add build-base git python3 font-noto --update-cache
- name: Dependencies (macOS, Windows)
- name: Dependencies (Python 3.10 - macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Dependencies (Node.js - macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Fix working directory ownership
if: matrix.container
run: chown root.root .

View File

@@ -14,7 +14,7 @@ When both a `width` and `height` are provided, the possible methods by which the
Some of these values are based on the [object-fit][1] CSS property.
When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
* `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.

View File

@@ -2,7 +2,17 @@
## v0.31 - *eagle*
Requires libvips v8.13.2
Requires libvips v8.13.3
### v0.31.2 - 4th November 2022
* Upgrade to libvips v8.13.3 for upstream bug fixes.
* Ensure manual flip, rotate, resize operation ordering (regression in 0.31.1)
[#3391](https://github.com/lovell/sharp/issues/3391)
* Ensure auto-rotation works without resize (regression in 0.31.1)
[#3422](https://github.com/lovell/sharp/issues/3422)
### v0.31.1 - 29th September 2022

View File

@@ -111,7 +111,7 @@ function isResizeExpected (options) {
*
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
*
* When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.31.1",
"version": "0.31.2",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -92,7 +92,7 @@
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "npm run test-lint && npm run test-unit && npm run test-licensing",
"test-lint": "semistandard && cpplint",
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha --slow=1000 --timeout=20000 ./test/unit/*.js",
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha --slow=1000 --timeout=30000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-leak": "./test/leak/leak.sh",
"docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
@@ -133,7 +133,7 @@
"detect-libc": "^2.0.1",
"node-addon-api": "^5.0.0",
"prebuild-install": "^7.1.1",
"semver": "^7.3.7",
"semver": "^7.3.8",
"simple-get": "^4.0.1",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
@@ -146,8 +146,8 @@
"extract-zip": "^2.0.1",
"icc": "^2.0.0",
"license-checker": "^25.0.1",
"mocha": "^10.0.0",
"mock-fs": "^5.1.4",
"mocha": "^10.1.0",
"mock-fs": "^5.2.0",
"nyc": "^15.1.0",
"prebuild": "^11.0.4",
"rimraf": "^3.0.2",
@@ -155,19 +155,19 @@
},
"license": "Apache-2.0",
"config": {
"libvips": "8.13.2",
"libvips": "8.13.3",
"integrity": {
"darwin-arm64v8": "sha512-4tsE/HMQDT9srV/ovSJlr7IxKnhvH9qpArCAf5Xpb/uNcAiT7BcZ+HYwX2lbf3UY8REB1TR4ThEL/lmPnzMUHw==",
"darwin-x64": "sha512-D4ZSvlgLpf+KzKB2OD+K8NWl0JKzzIbvWwIjjwBycIHTMkaiams3Kp/AQ/bKudqof02Ks6LtP0X4XWvCaoRoUA==",
"linux-arm64v8": "sha512-9ZvUM2NBluhoeUz9X7/zJ48xJ5d7KzI1cO6lsiv4HKo5fOYw/vEY28XodFJzhyfu9NuKxh3Hs9FtoQGNvvAFkw==",
"linux-armv6": "sha512-vu0R8DF0k7KseU62fzrJadHNk5oeJriFLVn3KxCKEfV+Wkj7rX4lQhiPmOuD7/wRcUY+GGdoZ52vysDwMQhfzA==",
"linux-armv7": "sha512-UdfhJTjGFgrwc3Kaos5G1ZAK2+t/16Prtnl6FAT+m7cG5EXzYAqzgvk4qtakAH7UTnVe8MUgOfbTLt0YiRpfsg==",
"linux-x64": "sha512-sv92VpPyN+3oBv0vi4wDjx51demGdtyhEjd+vDfC3h8S/RSuIUE9Pt/+dBFuf+iv9tRdIq9hH9vzAvsLVy6NYg==",
"linuxmusl-arm64v8": "sha512-TjhK/wHAS/m55l46T8PZ0qvlK+PKYFZGTQfh+c9aG8/z1v/VtG7TQOLNmPWfg0SFDTkXV7YqnJCqvgYLmJPZUg==",
"linuxmusl-x64": "sha512-/su96pn/H9+lDdnlM1xB2whWEoeEDJICFp/RNRJb0+bJPJhnL/IDVIhF4VnVNBq/9AlldBWii3hqMq5rY2eEAA==",
"win32-arm64v8": "sha512-UnSmwCcx3F5u4UOXyrdwTdYsuMK/RtQYc+1y+QxqIkBHiSL7dOlTIH/vKOSQvSaDQTPqxVLFt3wkMN1U7LZwyg==",
"win32-ia32": "sha512-KH/H6vpx5lJ6NEzLQmwxU/QnDg8p1Jxd+WKaPiyWmXq/HpwyKrZhi3WDoyKD4fLwnlfhAXEfVLZbUbhX21pDpQ==",
"win32-x64": "sha512-Xim5F21pqx7MuVQViaQNhSz24zWIiKHC9bm4KCdi7q/ytbvdMhm6bzWDI/mvFGNjI62NRB2SBkTTaqwJvM/pUg=="
"darwin-arm64v8": "sha512-xFgYt7CtQSZcWoyUdzPTDNHbioZIrZSEU+gkMxzH4Cgjhi4/N49UsonnIZhKQoTBGloAqEexHeMx4rYTQ2Kgvw==",
"darwin-x64": "sha512-6SivWKzu15aUMMohe0wg7sNYMPETVnOe40BuWsnKOgzl3o5FpQqNSgs+68Mi8Za3Qti9/DaR+H/fyD0x48Af2w==",
"linux-arm64v8": "sha512-b+iI9V/ehgDabXYRQcvqa5CEysh+1FQsgFmYc358StCrJCDahwNmsQdsiH1GOVd5WaWh5wHUGByPwMmFOO16Aw==",
"linux-armv6": "sha512-zRP2F+EiustLE4bXSH8AHCxwfemh9d+QuvmPjira/HL6uJOUuA7SyQgVV1TPwTQle2ioCNnKPm7FEB/MAiT+ug==",
"linux-armv7": "sha512-6OCChowE5lBXXXAZrnGdA9dVktg7UdODEBpE5qTroiAJYZv4yXRMgyDFYajok7du2NTgoklhxGk8d9+4vGv5hg==",
"linux-x64": "sha512-OTmlmP2r8ozGKdB96X+K5oQE1ojVZanqLqqKlwDpEnfixyIaDGYbVzcjWBNGU3ai/26bvkaCkjynnc2ecYcsuA==",
"linuxmusl-arm64v8": "sha512-Qh5Wi+bkKTohFYHzSPssfjMhIkD6z6EHbVmnwmWSsgY9zsUBStFp6+mKcNTQfP5YM5Mz06vJOkLHX2OzEr5TzA==",
"linuxmusl-x64": "sha512-DwB4Fs3+ISw9etaLCANkueZDdk758iOS+wNp4TKZkHdq0al6B/3Pk7OHLR8a9E3H6wYDD328u++dcJzip5tacA==",
"win32-arm64v8": "sha512-96r3W+O4BtX602B1MtxU5Ru4lKzRRTZqM4OQEBJ//TNL3fiCZdd9agD+RQBjaeR4KFIyBSt3F7IE425ZWmxz+w==",
"win32-ia32": "sha512-qfN1MsfQGek1QQd1UNW7JT+5K5Ne1suFQ2GpgpYm3JLSpIve/tz2vOGEGzvTVssOBADJvAkTDFt+yIi3PgU9pA==",
"win32-x64": "sha512-eb3aAmjbVVBVRbiYgebQwoxkAt69WI8nwmKlilSQ3kWqoc0pXfIe322rF2UR8ebbISCGvYRUfzD2r1k92RXISQ=="
},
"runtime": "napi",
"target": 7

View File

@@ -26,8 +26,8 @@
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 13) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION < 2)
#error "libvips version 8.13.2+ is required - please see https://sharp.pixelplumbing.com/install"
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION < 3)
#error "libvips version 8.13.3+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))

View File

@@ -81,35 +81,47 @@ class PipelineWorker : public Napi::AsyncWorker {
int pageHeight = sharp::GetPageHeight(image);
// Calculate angle of rotation
VipsAngle rotation;
bool flip = FALSE;
bool flop = FALSE;
VipsAngle rotation = VIPS_ANGLE_D0;
VipsAngle autoRotation = VIPS_ANGLE_D0;
bool autoFlip = FALSE;
bool autoFlop = FALSE;
if (baton->useExifOrientation) {
// Rotate and flip image according to Exif orientation
std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
image = sharp::RemoveExifOrientation(image);
} else {
rotation = CalculateAngleRotation(baton->angle);
}
// Rotate pre-extract
bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
(rotation != VIPS_ANGLE_D0 || flip || flop || baton->rotationAngle != 0.0);
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
autoFlip || baton->flip || autoFlop || baton->flop ||
baton->rotationAngle != 0.0);
if (shouldRotateBefore) {
if (autoRotation != VIPS_ANGLE_D0) {
image = image.rot(autoRotation);
autoRotation = VIPS_ANGLE_D0;
}
if (autoFlip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
autoFlip = FALSE;
} else if (baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
baton->flip = FALSE;
}
if (autoFlop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
autoFlop = FALSE;
} else if (baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
baton->flop = FALSE;
}
if (rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation);
rotation = VIPS_ANGLE_D0;
}
if (flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
}
if (flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
}
if (rotation != VIPS_ANGLE_D0 || flip || flop) {
image = sharp::RemoveExifOrientation(image);
}
flop = FALSE;
flip = FALSE;
if (baton->rotationAngle != 0.0) {
MultiPageUnsupported(nPages, "Rotate");
std::vector<double> background;
@@ -150,7 +162,9 @@ class PipelineWorker : public Napi::AsyncWorker {
int targetResizeHeight = baton->height;
// Swap input output width and height when rotating by 90 or 270 degrees
bool swap = !baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270);
bool swap = !baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270 ||
autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270);
// Shrink to pageHeight, so we work for multi-page images
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
@@ -367,30 +381,21 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("kernel", baton->kernel));
}
// Auto-rotate post-extract
if (autoRotation != VIPS_ANGLE_D0) {
image = image.rot(autoRotation);
}
// Flip (mirror about Y axis)
if (baton->flip || flip) {
if (baton->flip || autoFlip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
image = sharp::RemoveExifOrientation(image);
}
// Flop (mirror about X axis)
if (baton->flop || flop) {
if (baton->flop || autoFlop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
image = sharp::RemoveExifOrientation(image);
}
// Rotate post-extract 90-angle
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
if (rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation);
if (flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
flip = FALSE;
}
if (flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
flop = FALSE;
}
image = sharp::RemoveExifOrientation(image);
}
// Join additional color channels to the image

View File

@@ -140,6 +140,7 @@ describe('PNG', function () {
height: 68,
space: 'srgb',
channels: 3,
density: 72,
depth: 'uchar',
isProgressive: false,
paletteBitDepth: 8,

View File

@@ -8,16 +8,43 @@ const fixtures = require('../fixtures');
describe('Rotation', function () {
['Landscape', 'Portrait'].forEach(function (orientation) {
[1, 2, 3, 4, 5, 6, 7, 8].forEach(function (exifTag) {
it('Input image has Orientation EXIF tag value of (' + exifTag + '), auto-rotate', function (done) {
sharp(fixtures['inputJpgWith' + orientation + 'Exif' + exifTag])
const input = fixtures[`inputJpgWith${orientation}Exif${exifTag}`];
const expectedOutput = fixtures.expected(`${orientation}_${exifTag}-out.jpg`);
it(`Auto-rotate ${orientation} image with EXIF Orientation ${exifTag}`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600];
sharp(input)
.rotate()
.resize(320)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(orientation === 'Landscape' ? 240 : 427, info.height);
fixtures.assertSimilar(fixtures.expected(orientation + '_' + exifTag + '-out.jpg'), data, done);
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
it(`Auto-rotate then resize ${orientation} image with EXIF Orientation ${exifTag}`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [320, 240] : [320, 427];
sharp(input)
.rotate()
.resize({ width: 320 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
it(`Resize then auto-rotate ${orientation} image with EXIF Orientation ${exifTag}`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape'
? (exifTag < 5) ? [320, 240] : [320, 240]
: [320, 427];
sharp(input)
.resize({ width: 320 })
.rotate()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
});
@@ -418,4 +445,29 @@ describe('Rotation', function () {
assert.strictEqual(g, 73);
assert.strictEqual(b, 52);
});
it('Flip and rotate ordering', async () => {
const [r, g, b] = await sharp(fixtures.inputJpgWithPortraitExif5)
.flip()
.rotate(90)
.raw()
.toBuffer();
assert.strictEqual(r, 55);
assert.strictEqual(g, 65);
assert.strictEqual(b, 31);
});
it('Flip, rotate and resize ordering', async () => {
const [r, g, b] = await sharp(fixtures.inputJpgWithPortraitExif5)
.flip()
.rotate(90)
.resize(449)
.raw()
.toBuffer();
assert.strictEqual(r, 54);
assert.strictEqual(g, 64);
assert.strictEqual(b, 30);
});
});