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 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 ### v0.30.1 - 9th February 2022
* Allow use of `toBuffer` and `toFile` on the same instance. * 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); throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
} }
} }
return composite; return composite;
}); });
return this; return this;

View File

@ -581,6 +581,8 @@ class PipelineWorker : public Napi::AsyncWorker {
// Composite // Composite
if (shouldComposite) { if (shouldComposite) {
std::vector<VImage> images = { image };
std::vector<int> modes, xs, ys;
for (Composite *composite : baton->composite) { for (Composite *composite : baton->composite) {
VImage compositeImage; VImage compositeImage;
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; 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 // gravity was used for extract_area, set it back to its default value of 0
composite->gravity = 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); compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!sharp::HasAlpha(compositeImage)) { if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage, 1); compositeImage = sharp::EnsureAlpha(compositeImage, 1);
} }
if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
// Calculate position // Calculate position
int left; int left;
int top; int top;
@ -649,12 +651,12 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(), std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->gravity); compositeImage.width(), compositeImage.height(), composite->gravity);
} }
// Composite images.push_back(compositeImage);
image = image.composite2(compositeImage, composite->mode, VImage::option() modes.push_back(composite->mode);
->set("premultiplied", TRUE) xs.push_back(left);
->set("x", left) ys.push_back(top);
->set("y", top));
} }
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
} }
// Reverse premultiplication after all transformations: // 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 // Test
describe('composite', () => { describe('composite', () => {
it('blend', () => Promise.all( blends.forEach(blend => {
blends.map(blend => { it(`blend ${blend}`, async () => {
const filename = `composite.blend.${blend}.png`; const filename = `composite.blend.${blend}.png`;
const actual = fixtures.path(`output.${filename}`); const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename); const expected = fixtures.expected(filename);
return sharp(redRect) await sharp(redRect)
.composite([{ .composite([{
input: blueRect, input: blueRect,
blend blend
}]) }])
.toFile(actual) .toFile(actual);
.then(() => { fixtures.assertMaxColourDistance(actual, expected);
fixtures.assertMaxColourDistance(actual, expected); });
}); });
})
));
it('premultiplied true', () => { it('premultiplied true', () => {
const filename = 'composite.premultiplied.png'; const filename = 'composite.premultiplied.png';
@ -121,11 +119,11 @@ describe('composite', () => {
}); });
}); });
it('multiple', () => { it('multiple', async () => {
const filename = 'composite-multiple.png'; const filename = 'composite-multiple.png';
const actual = fixtures.path(`output.${filename}`); const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename); const expected = fixtures.expected(filename);
return sharp(redRect) await sharp(redRect)
.composite([{ .composite([{
input: blueRect, input: blueRect,
gravity: 'northeast' gravity: 'northeast'
@ -133,10 +131,8 @@ describe('composite', () => {
input: greenRect, input: greenRect,
gravity: 'southwest' gravity: 'southwest'
}]) }])
.toFile(actual) .toFile(actual);
.then(() => { fixtures.assertMaxColourDistance(actual, expected);
fixtures.assertMaxColourDistance(actual, expected);
});
}); });
it('zero offset', done => { it('zero offset', done => {

View File

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