Compare commits

..

23 Commits

Author SHA1 Message Date
Lovell Fuller
16ea04fe80 Release v0.32.2 2023-07-11 11:47:37 +01:00
Lovell Fuller
9c547dc321 Use copy rather than cache to prevent affine overcompute
More predictable behaviour, see commit 14c3346 for context
2023-07-10 13:56:42 +01:00
Lovell Fuller
5522060e9e Limit HEIF output dimensions to 16384x16384
This is a slightly breaking change to sync with the behaviour of a
forthcoming libvips patch release. It also matches the libavif
limit. Having an arbitrary limit is safer than no limit.
2023-07-10 10:24:14 +01:00
Lovell Fuller
d2f0fa855b Tests: loosen threshold for affine rotate then extract
Ignores rounding errors under Rosetta emulation
2023-07-10 08:12:13 +01:00
Lovell Fuller
2bb3ea8170 Bump deps 2023-07-09 11:57:05 +01:00
Lovell Fuller
3434eef5b9 Guard use of smartcrop premultiplied option #3710 2023-07-09 09:57:20 +01:00
Lovell Fuller
2f67823c3d Allow seq read for EXIF-based auto-orient #3725 2023-07-09 09:26:58 +01:00
Lovell Fuller
38c760cdd7 Update to latest (temporary) prebuild patch 2023-07-09 09:10:24 +01:00
Lovell Fuller
14c3346800 Prevent over-compute in affine rotate #3722 2023-07-09 09:04:07 +01:00
Lovell Fuller
0da55bab7e Tests: remove unused dependency 2023-06-29 09:11:25 +01:00
Lovell Fuller
cfb659f576 Bump deps 2023-06-23 08:19:41 +01:00
Lovell Fuller
cc5ac5385f Docs: clarify use of extract before composite 2023-06-23 08:08:39 +01:00
Lovell Fuller
93fafb0c18 CI: Upgrade to latest git v2 within centos 7 containers 2023-06-05 12:32:47 +01:00
Lovell Fuller
41e3c8ca09 Temporarily use patched prebuild with node-gyp v9 2023-06-05 09:45:12 +01:00
Lovell Fuller
da61ea0199 Docs: changelog and credit for #3674 2023-06-05 09:35:07 +01:00
BJJ
7e6a70af44 Improve detection of jp2 filename extensions #3674 2023-06-05 09:31:25 +01:00
Lovell Fuller
f5845c7e61 Ensure exceptions are not thrown when terminating #3569 2023-06-03 11:51:44 +01:00
Lovell Fuller
eb1e53db83 Bump deps 2023-06-03 11:51:12 +01:00
Lovell Fuller
3340120aea Types: include base input options for composite #3669 2023-05-16 13:55:28 +01:00
Lovell Fuller
de0fc07092 Ensure same access method for all inputs #3669 2023-05-16 13:53:31 +01:00
Lovell Fuller
dc4b39f73f Docs: multi-page images cannot be flipped 2023-05-13 08:54:21 +01:00
Lovell Fuller
e873978e53 Docs: clarify which axis is used when mirroring 2023-05-11 10:24:24 +01:00
Lovell Fuller
5255964c79 Docs: ensure headings with digits appear 2023-04-27 10:23:28 +01:00
21 changed files with 130 additions and 43 deletions

View File

@@ -66,6 +66,7 @@ jobs:
if: contains(matrix.container, 'centos')
run: |
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
yum install -y https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
yum install -y centos-release-scl
yum install -y devtoolset-11-gcc-c++ make git python3 nodejs fontconfig google-noto-sans-fonts
echo "/opt/rh/devtoolset-11/root/usr/bin" >> $GITHUB_PATH

View File

@@ -70,7 +70,9 @@
}, {
'target_name': 'sharp-<(platform_and_arch)',
'defines': [
'NAPI_VERSION=7'
'NAPI_VERSION=7',
'NODE_ADDON_API_DISABLE_DEPRECATED',
'NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'
],
'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',

View File

@@ -4,7 +4,7 @@ Composite image(s) over the processed (resized, extracted etc.) image.
The images to composite must be the same size or smaller than the processed image.
If both `top` and `left` options are provided, they take precedence over `gravity`.
Any resize or rotate operations in the same processing pipeline
Any resize, rotate or extract operations in the same processing pipeline
will always be applied to the input image before composition.
The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,

View File

@@ -57,9 +57,13 @@ const resizeThenRotate = await sharp(input)
## flip
Flip the image about the vertical Y axis. This always occurs before rotation, if any.
Mirror the image vertically (up-down) about the x-axis.
This always occurs before rotation, if any.
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
This operation does not work correctly with multi-page images.
| Param | Type | Default |
@@ -73,7 +77,9 @@ const output = await sharp(input).flip().toBuffer();
## flop
Flop the image about the horizontal X axis. This always occurs before rotation, if any.
Mirror the image horizontally (left-right) about the y-axis.
This always occurs before rotation, if any.
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.

View File

@@ -374,9 +374,9 @@ await sharp('in.gif', { animated: true })
.gif({ interFrameMaxError: 8 })
.toFile('optim.gif');
```
<a name="jp2"></a>
## jp
## jp2
Use these JP2 options for output image.
Requires libvips compiled with support for OpenJPEG.

View File

@@ -29,8 +29,8 @@ const jsdoc2md = require('jsdoc-to-markdown');
});
const cleanMarkdown = markdown
.replace(/(## [A-Za-z]+)[^\n]*/g, '$1') // simplify headings to match those of documentationjs, ensures existing URLs work
.replace(/<a name="[A-Za-z+]+"><\/a>/g, '') // remove anchors, let docute add these (at markdown to HTML render time)
.replace(/(## [A-Za-z0-9]+)[^\n]*/g, '$1') // simplify headings to match those of documentationjs, ensures existing URLs work
.replace(/<a name="[A-Za-z0-9+]+"><\/a>/g, '') // remove anchors, let docute add these (at markdown to HTML render time)
.replace(/\*\*Kind\*\*: global[^\n]+/g, '') // remove all "global" Kind labels (requires JSDoc refactoring)
.trim();

View File

@@ -4,6 +4,29 @@
Requires libvips v8.14.2
### v0.32.2 - 11th July 2023
* Limit HEIF output dimensions to 16384x16384, matches libvips.
* Ensure exceptions are not thrown when terminating.
[#3569](https://github.com/lovell/sharp/issues/3569)
* Ensure the same access method is used for all inputs (regression in 0.32.0).
[#3669](https://github.com/lovell/sharp/issues/3669)
* Improve detection of jp2 filename extensions.
[#3674](https://github.com/lovell/sharp/pull/3674)
[@bianjunjie1981](https://github.com/bianjunjie1981)
* Guard use of smartcrop premultiplied option to prevent warning (regression in 0.32.1).
[#3710](https://github.com/lovell/sharp/issues/3710)
* Prevent over-compute in affine-based rotate before resize.
[#3722](https://github.com/lovell/sharp/issues/3722)
* Allow sequential read for EXIF-based auto-orientation.
[#3725](https://github.com/lovell/sharp/issues/3725)
### v0.32.1 - 27th April 2023
* Add experimental `unflatten` operation.

View File

@@ -272,3 +272,6 @@ GitHub: https://github.com/janaz
Name: Lachlan Newman
GitHub: https://github.com/LachlanNewman
Name: BJJ
GitHub: https://github.com/bianjunjie1981

File diff suppressed because one or more lines are too long

View File

@@ -46,7 +46,7 @@ const blend = {
* The images to composite must be the same size or smaller than the processed image.
* If both `top` and `left` options are provided, they take precedence over `gravity`.
*
* Any resize or rotate operations in the same processing pipeline
* Any resize, rotate or extract operations in the same processing pipeline
* will always be applied to the input image before composition.
*
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,

21
lib/index.d.ts vendored
View File

@@ -1350,9 +1350,9 @@ declare namespace sharp {
grayscale?: boolean | undefined;
}
interface OverlayOptions {
interface OverlayOptions extends SharpOptions {
/** Buffer containing image data, String containing the path to an image file, or Create object */
input?: string | Buffer | { create: Create } | { text: CreateText } | undefined;
input?: string | Buffer | { create: Create } | { text: CreateText } | { raw: CreateRaw } | undefined;
/** how to blend this image with the image below. (optional, default `'over'`) */
blend?: Blend | undefined;
/** gravity at which to place the overlay. (optional, default 'centre') */
@@ -1363,25 +1363,8 @@ declare namespace sharp {
left?: number | undefined;
/** set to true to repeat the overlay image across the entire image with the given gravity. (optional, default false) */
tile?: boolean | undefined;
/** number representing the DPI for vector overlay image. (optional, default 72) */
density?: number | undefined;
/** describes overlay when using raw pixel data. */
raw?: Raw | undefined;
/** Set to true to avoid premultipling the image below. Equivalent to the --premultiplied vips option. */
premultiplied?: boolean | undefined;
/** Set to true to read all frames/pages of an animated image. (optional, default false). */
animated?: boolean | undefined;
/**
* When to abort processing of invalid pixel data, one of (in order of sensitivity):
* 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning')
*/
failOn?: FailOnOptions | undefined;
/**
* Do not process input images where the number of pixels (width x height) exceeds this limit.
* Assumes image dimensions contained in the input metadata can be trusted.
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default 268402689)
*/
limitInputPixels?: number | boolean | undefined;
}
interface TileOptions {

View File

@@ -80,9 +80,13 @@ function rotate (angle, options) {
}
/**
* Flip the image about the vertical Y axis. This always occurs before rotation, if any.
* Mirror the image vertically (up-down) about the x-axis.
* This always occurs before rotation, if any.
*
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
*
* This operation does not work correctly with multi-page images.
*
* @example
* const output = await sharp(input).flip().toBuffer();
*
@@ -95,7 +99,9 @@ function flip (flip) {
}
/**
* Flop the image about the horizontal X axis. This always occurs before rotation, if any.
* Mirror the image horizontally (left-right) about the y-axis.
* This always occurs before rotation, if any.
*
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
*
* @example

View File

@@ -29,7 +29,7 @@ const formats = new Map([
['jxl', 'jxl']
]);
const jp2Regex = /\.jp[2x]|j2[kc]$/i;
const jp2Regex = /\.(jp[2x]|j2[kc])$/i;
const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG');
@@ -75,7 +75,7 @@ function toFile (fileOut, callback) {
err = new Error('Missing output file path');
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
err = new Error('Cannot use same file for input and output');
} else if (jp2Regex.test(fileOut) && !this.constructor.format.jp2k.output.file) {
} else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
err = errJp2Save();
}
if (err) {

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.32.1",
"version": "0.32.2",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -136,9 +136,9 @@
"detect-libc": "^2.0.1",
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.0",
"semver": "^7.5.4",
"simple-get": "^4.0.1",
"tar-fs": "^2.1.1",
"tar-fs": "^3.0.4",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
@@ -153,7 +153,7 @@
"mocha": "^10.2.0",
"mock-fs": "^5.2.0",
"nyc": "^15.1.0",
"prebuild": "^11.0.4",
"prebuild": "lovell/prebuild#add-nodejs-20-drop-nodejs-10-and-12",
"semistandard": "^16.0.1",
"tsd": "^0.28.1"
},

View File

@@ -669,6 +669,10 @@ namespace sharp {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the GIF format");
}
} else if (imageType == ImageType::HEIF) {
if (image.width() > 16384 || height > 16384) {
throw vips::VError("Processed image is too large for the HEIF format");
}
}
}

View File

@@ -79,6 +79,9 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate and flip image according to Exif orientation
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
image = sharp::RemoveExifOrientation(image);
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL && (autoRotation != VIPS_ANGLE_D0 || autoFlip)) {
image = image.copy_memory();
}
} else {
rotation = CalculateAngleRotation(baton->angle);
}
@@ -116,7 +119,7 @@ class PipelineWorker : public Napi::AsyncWorker {
MultiPageUnsupported(nPages, "Rotate");
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
}
}
@@ -380,11 +383,11 @@ class PipelineWorker : public Napi::AsyncWorker {
if (autoRotation != VIPS_ANGLE_D0) {
image = image.rot(autoRotation);
}
// Flip (mirror about Y axis)
// Mirror vertically (up-down) about the x-axis
if (baton->flip || autoFlip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
}
// Flop (mirror about X axis)
// Mirror horizontally (left-right) about the y-axis
if (baton->flop || autoFlop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
}
@@ -399,6 +402,7 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
baton->joinChannelIn[i]->access = baton->input->access;
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
image = image.bandjoin(joinImage);
@@ -473,7 +477,9 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.smartcrop(baton->width, baton->height, VImage::option()
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 15)
->set("premultiplied", shouldPremultiplyAlpha)
#endif
->set("attention_x", &attention_x)
->set("attention_y", &attention_y));
baton->hasCropOffset = true;
@@ -608,6 +614,7 @@ class PipelineWorker : public Napi::AsyncWorker {
for (Composite *composite : baton->composite) {
VImage compositeImage;
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
composite->input->access = baton->input->access;
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
// Verify within current dimensions
@@ -706,6 +713,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->boolean != nullptr) {
VImage booleanImage;
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
baton->boolean->access = baton->input->access;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
@@ -933,6 +941,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "heif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
@@ -1122,6 +1131,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
@@ -1673,7 +1683,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->angle != 0 ||
baton->rotationAngle != 0.0 ||
baton->tileAngle != 0 ||
baton->useExifOrientation ||
baton->flip ||
baton->claheWidth != 0 ||
!baton->affineMatrix.empty()

View File

@@ -14,8 +14,7 @@
"benchmark": "2.1.4",
"gm": "1.25.0",
"imagemagick": "0.1.3",
"jimp": "0.22.7",
"semver": "7.3.8"
"jimp": "0.22.7"
},
"optionalDependencies": {
"@tensorflow/tfjs-node": "4.2.0",

View File

@@ -637,3 +637,17 @@ sharp('input.png').composite([
sharp('input.png').tile({
basename: 'output.dz.tiles',
});
// https://github.com/lovell/sharp/issues/3669
sharp(input).composite([
{
raw: {
width: 1,
height: 1,
channels: 1,
premultiplied: false,
},
sequentialRead: false,
unlimited: true,
}
]);

View File

@@ -130,4 +130,18 @@ describe('AVIF', () => {
width: 32
});
});
it('Invalid width - too large', async () =>
assert.rejects(
() => sharp({ create: { width: 16385, height: 16, channels: 3, background: 'red' } }).avif().toBuffer(),
/Processed image is too large for the HEIF format/
)
);
it('Invalid height - too large', async () =>
assert.rejects(
() => sharp({ create: { width: 16, height: 16385, channels: 3, background: 'red' } }).avif().toBuffer(),
/Processed image is too large for the HEIF format/
)
);
});

View File

@@ -25,6 +25,13 @@ describe('JP2 output', () => {
/JP2 output requires libvips with support for OpenJPEG/
)
);
it('File with JP2-like suffix should not fail due to missing OpenJPEG', () => {
const output = fixtures.path('output.failj2c');
return assert.doesNotReject(
async () => sharp(fixtures.inputPngWithOneColor).toFile(output)
);
});
} else {
it('JP2 Buffer to PNG Buffer', () => {
sharp(fs.readFileSync(fixtures.inputJp2))

View File

@@ -473,4 +473,20 @@ describe('Rotation', function () {
assert.strictEqual(g, 64);
assert.strictEqual(b, 30);
});
it('Resize after affine-based rotation does not overcompute', async () =>
sharp({
create: {
width: 4640,
height: 2610,
channels: 3,
background: 'black'
}
})
.rotate(28)
.resize({ width: 640, height: 360 })
.raw()
.timeout({ seconds: 5 })
.toBuffer()
);
});