Skip to content

Commit

Permalink
Improve performance and accuracy of multi-image composite #2286
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Feb 16, 2022
1 parent 7f83ecd commit c620025
Show file tree
Hide file tree
Showing 10 changed files with 27 additions and 29 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.md
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion lib/composite.js
Expand Up @@ -162,7 +162,6 @@ function composite (images) {
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
}
}

return composite;
});
return this;
Expand Down
16 changes: 9 additions & 7 deletions src/pipeline.cc
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand Down
Binary file modified test/fixtures/expected/composite-multiple.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/expected/composite.blend.dest-over.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/expected/composite.blend.over.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/expected/composite.blend.saturate.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/expected/composite.blend.xor.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 11 additions & 15 deletions test/unit/composite.js
Expand Up @@ -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';
Expand Down Expand Up @@ -121,22 +119,20 @@ 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'
}, {
input: greenRect,
gravity: 'southwest'
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
.toFile(actual);
fixtures.assertMaxColourDistance(actual, expected);
});

it('zero offset', done => {
Expand Down
8 changes: 2 additions & 6 deletions test/unit/extend.js
Expand Up @@ -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'
Expand All @@ -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]);
});
});

0 comments on commit c620025

Please sign in to comment.