Improve performance and accuracy of multi-image composite #2286

This commit is contained in:
Lovell Fuller 2022-02-16 18:56:35 +00:00
parent 7f83ecd255
commit c62002554b
10 changed files with 27 additions and 29 deletions

View File

@ -4,6 +4,11 @@
Requires libvips v8.12.2
### v0.30.2 - TBD
* Improve performance and accuracy when compositing multiple images.
[#2286](https://github.com/lovell/sharp/issues/2286)
### v0.30.1 - 9th February 2022
* Allow use of `toBuffer` and `toFile` on the same instance.

View File

@ -162,7 +162,6 @@ function composite (images) {
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
}
}
return composite;
});
return this;

View File

@ -581,6 +581,8 @@ class PipelineWorker : public Napi::AsyncWorker {
// Composite
if (shouldComposite) {
std::vector<VImage> images = { image };
std::vector<int> modes, xs, ys;
for (Composite *composite : baton->composite) {
VImage compositeImage;
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
@ -626,12 +628,12 @@ class PipelineWorker : public Napi::AsyncWorker {
// gravity was used for extract_area, set it back to its default value of 0
composite->gravity = 0;
}
// Ensure image to composite is sRGB with premultiplied alpha
// Ensure image to composite is sRGB with unpremultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
}
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
// Calculate position
int left;
int top;
@ -649,12 +651,12 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->gravity);
}
// Composite
image = image.composite2(compositeImage, composite->mode, VImage::option()
->set("premultiplied", TRUE)
->set("x", left)
->set("y", top));
images.push_back(compositeImage);
modes.push_back(composite->mode);
xs.push_back(left);
ys.push_back(top);
}
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
}
// Reverse premultiplication after all transformations:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 286 B

View File

@ -45,22 +45,20 @@ const blends = [
// Test
describe('composite', () => {
it('blend', () => Promise.all(
blends.map(blend => {
blends.forEach(blend => {
it(`blend ${blend}`, async () => {
const filename = `composite.blend.${blend}.png`;
const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename);
return sharp(redRect)
await sharp(redRect)
.composite([{
input: blueRect,
blend
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
})
));
.toFile(actual);
fixtures.assertMaxColourDistance(actual, expected);
});
});
it('premultiplied true', () => {
const filename = 'composite.premultiplied.png';
@ -121,11 +119,11 @@ describe('composite', () => {
});
});
it('multiple', () => {
it('multiple', async () => {
const filename = 'composite-multiple.png';
const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename);
return sharp(redRect)
await sharp(redRect)
.composite([{
input: blueRect,
gravity: 'northeast'
@ -133,10 +131,8 @@ describe('composite', () => {
input: greenRect,
gravity: 'southwest'
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
.toFile(actual);
fixtures.assertMaxColourDistance(actual, expected);
});
it('zero offset', done => {

View File

@ -140,7 +140,7 @@ describe('Extend', function () {
});
it('Premultiply background when compositing', async () => {
const background = '#bf1942cc';
const background = { r: 191, g: 25, b: 66, alpha: 0.8 };
const data = await sharp({
create: {
width: 1, height: 1, channels: 4, background: '#fff0'
@ -158,10 +158,6 @@ describe('Extend', function () {
})
.raw()
.toBuffer();
const [r1, g1, b1, a1, r2, g2, b2, a2] = data;
assert.strictEqual(true, Math.abs(r2 - r1) < 2);
assert.strictEqual(true, Math.abs(g2 - g1) < 2);
assert.strictEqual(true, Math.abs(b2 - b1) < 2);
assert.strictEqual(true, Math.abs(a2 - a1) < 2);
assert.deepStrictEqual(Array.from(data), [191, 25, 65, 204, 238, 31, 82, 204]);
});
});