Skip to content

Commit c620025

Browse files
committed
Improve performance and accuracy of multi-image composite #2286
1 parent 7f83ecd commit c620025

File tree

10 files changed

+27
-29
lines changed

10 files changed

+27
-29
lines changed

docs/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
Requires libvips v8.12.2
66

7+
### v0.30.2 - TBD
8+
9+
* Improve performance and accuracy when compositing multiple images.
10+
[#2286](https://github.com/lovell/sharp/issues/2286)
11+
712
### v0.30.1 - 9th February 2022
813

914
* Allow use of `toBuffer` and `toFile` on the same instance.

lib/composite.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ function composite (images) {
162162
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
163163
}
164164
}
165-
166165
return composite;
167166
});
168167
return this;

src/pipeline.cc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ class PipelineWorker : public Napi::AsyncWorker {
581581

582582
// Composite
583583
if (shouldComposite) {
584+
std::vector<VImage> images = { image };
585+
std::vector<int> modes, xs, ys;
584586
for (Composite *composite : baton->composite) {
585587
VImage compositeImage;
586588
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
@@ -626,12 +628,12 @@ class PipelineWorker : public Napi::AsyncWorker {
626628
// gravity was used for extract_area, set it back to its default value of 0
627629
composite->gravity = 0;
628630
}
629-
// Ensure image to composite is sRGB with premultiplied alpha
631+
// Ensure image to composite is sRGB with unpremultiplied alpha
630632
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
631633
if (!sharp::HasAlpha(compositeImage)) {
632634
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
633635
}
634-
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
636+
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
635637
// Calculate position
636638
int left;
637639
int top;
@@ -649,12 +651,12 @@ class PipelineWorker : public Napi::AsyncWorker {
649651
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
650652
compositeImage.width(), compositeImage.height(), composite->gravity);
651653
}
652-
// Composite
653-
image = image.composite2(compositeImage, composite->mode, VImage::option()
654-
->set("premultiplied", TRUE)
655-
->set("x", left)
656-
->set("y", top));
654+
images.push_back(compositeImage);
655+
modes.push_back(composite->mode);
656+
xs.push_back(left);
657+
ys.push_back(top);
657658
}
659+
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
658660
}
659661

660662
// Reverse premultiplication after all transformations:
98 Bytes
Loading
Loading
95 Bytes
Loading
Loading
94 Bytes
Loading

test/unit/composite.js

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,20 @@ const blends = [
4545

4646
// Test
4747
describe('composite', () => {
48-
it('blend', () => Promise.all(
49-
blends.map(blend => {
48+
blends.forEach(blend => {
49+
it(`blend ${blend}`, async () => {
5050
const filename = `composite.blend.${blend}.png`;
5151
const actual = fixtures.path(`output.${filename}`);
5252
const expected = fixtures.expected(filename);
53-
return sharp(redRect)
53+
await sharp(redRect)
5454
.composite([{
5555
input: blueRect,
5656
blend
5757
}])
58-
.toFile(actual)
59-
.then(() => {
60-
fixtures.assertMaxColourDistance(actual, expected);
61-
});
62-
})
63-
));
58+
.toFile(actual);
59+
fixtures.assertMaxColourDistance(actual, expected);
60+
});
61+
});
6462

6563
it('premultiplied true', () => {
6664
const filename = 'composite.premultiplied.png';
@@ -121,22 +119,20 @@ describe('composite', () => {
121119
});
122120
});
123121

124-
it('multiple', () => {
122+
it('multiple', async () => {
125123
const filename = 'composite-multiple.png';
126124
const actual = fixtures.path(`output.${filename}`);
127125
const expected = fixtures.expected(filename);
128-
return sharp(redRect)
126+
await sharp(redRect)
129127
.composite([{
130128
input: blueRect,
131129
gravity: 'northeast'
132130
}, {
133131
input: greenRect,
134132
gravity: 'southwest'
135133
}])
136-
.toFile(actual)
137-
.then(() => {
138-
fixtures.assertMaxColourDistance(actual, expected);
139-
});
134+
.toFile(actual);
135+
fixtures.assertMaxColourDistance(actual, expected);
140136
});
141137

142138
it('zero offset', done => {

test/unit/extend.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe('Extend', function () {
140140
});
141141

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy