diff --git a/docs/api-composite.md b/docs/api-composite.md index 83490cc7..c571edd6 100644 --- a/docs/api-composite.md +++ b/docs/api-composite.md @@ -31,6 +31,7 @@ and [https://www.cairographics.org/operators/][2] - `images[].top` **[Number][7]?** the pixel offset from the top edge. - `images[].left` **[Number][7]?** the pixel offset from the left edge. - `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`) + - `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`) - `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`) - `images[].raw` **[Object][4]?** describes overlay when using raw pixel data. - `images[].raw.width` **[Number][7]?** diff --git a/lib/composite.js b/lib/composite.js index d934bb80..24f61e4a 100644 --- a/lib/composite.js +++ b/lib/composite.js @@ -105,7 +105,8 @@ function composite (images) { tile: false, left: -1, top: -1, - gravity: 0 + gravity: 0, + premultiplied: false }; if (is.defined(image.blend)) { if (is.string(blend[image.blend])) { @@ -147,6 +148,14 @@ function composite (images) { throw is.invalidParameterError('gravity', 'valid gravity', image.gravity); } } + if (is.defined(image.premultiplied)) { + if (is.bool(image.premultiplied)) { + composite.premultiplied = image.premultiplied; + } else { + throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied); + } + } + return composite; }); return this; diff --git a/src/pipeline.cc b/src/pipeline.cc index e68def8c..8cd3b89c 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -591,7 +591,7 @@ class PipelineWorker : public Nan::AsyncWorker { if (!HasAlpha(compositeImage)) { compositeImage = sharp::EnsureAlpha(compositeImage); } - compositeImage = compositeImage.premultiply(); + if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); // Calculate position int left; int top; @@ -1230,6 +1230,7 @@ NAN_METHOD(pipeline) { composite->left = AttrTo(compositeObject, "left"); composite->top = AttrTo(compositeObject, "top"); composite->tile = AttrTo(compositeObject, "tile"); + composite->premultiplied = AttrTo(compositeObject, "premultiplied"); baton->composite.push_back(composite); } // Resize options diff --git a/src/pipeline.h b/src/pipeline.h index 50e62e6b..18db0be3 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -41,6 +41,7 @@ struct Composite { int left; int top; bool tile; + bool premultiplied; Composite(): input(nullptr), @@ -48,7 +49,8 @@ struct Composite { gravity(0), left(-1), top(-1), - tile(false) {} + tile(false), + premultiplied(false) {} }; struct PipelineBaton { diff --git a/test/fixtures/expected/expected.absent.composite.premultiplied.png b/test/fixtures/expected/expected.absent.composite.premultiplied.png new file mode 100755 index 00000000..09d90aba Binary files /dev/null and b/test/fixtures/expected/expected.absent.composite.premultiplied.png differ diff --git a/test/fixtures/expected/expected.false.composite.premultiplied.png b/test/fixtures/expected/expected.false.composite.premultiplied.png new file mode 100755 index 00000000..09d90aba Binary files /dev/null and b/test/fixtures/expected/expected.false.composite.premultiplied.png differ diff --git a/test/fixtures/expected/expected.true.composite.premultiplied.png b/test/fixtures/expected/expected.true.composite.premultiplied.png new file mode 100755 index 00000000..85f59a51 Binary files /dev/null and b/test/fixtures/expected/expected.true.composite.premultiplied.png differ diff --git a/test/fixtures/input.above.composite.premultiplied.png b/test/fixtures/input.above.composite.premultiplied.png new file mode 100755 index 00000000..dfd105e4 Binary files /dev/null and b/test/fixtures/input.above.composite.premultiplied.png differ diff --git a/test/fixtures/input.below.composite.premultiplied.png b/test/fixtures/input.below.composite.premultiplied.png new file mode 100755 index 00000000..e2bcb6b0 Binary files /dev/null and b/test/fixtures/input.below.composite.premultiplied.png differ diff --git a/test/unit/composite.js b/test/unit/composite.js old mode 100644 new mode 100755 index 7466b128..d5912f6a --- a/test/unit/composite.js +++ b/test/unit/composite.js @@ -62,6 +62,65 @@ describe('composite', () => { }) )); + it('premultiplied true', () => { + const filename = 'composite.premultiplied.png'; + const below = fixtures.path(`input.below.${filename}`); + const above = fixtures.path(`input.above.${filename}`); + const actual = fixtures.path(`output.true.${filename}`); + const expected = fixtures.expected(`expected.true.${filename}`); + return sharp(below) + .composite([{ + input: above, + blend: 'color-burn', + top: 0, + left: 0, + premultiplied: true + }]) + .toFile(actual) + .then(() => { + fixtures.assertMaxColourDistance(actual, expected); + }); + }); + + it('premultiplied false', () => { + const filename = 'composite.premultiplied.png'; + const below = fixtures.path(`input.below.${filename}`); + const above = fixtures.path(`input.above.${filename}`); + const actual = fixtures.path(`output.false.${filename}`); + const expected = fixtures.expected(`expected.false.${filename}`); + return sharp(below) + .composite([{ + input: above, + blend: 'color-burn', + top: 0, + left: 0, + premultiplied: false + }]) + .toFile(actual) + .then(() => { + fixtures.assertMaxColourDistance(actual, expected); + }); + }); + + it('premultiplied absent', () => { + const filename = 'composite.premultiplied.png'; + const below = fixtures.path(`input.below.${filename}`); + const above = fixtures.path(`input.above.${filename}`); + const actual = fixtures.path(`output.absent.${filename}`); + const expected = fixtures.expected(`expected.absent.${filename}`); + return sharp(below) + .composite([{ + input: above, + blend: 'color-burn', + top: 0, + left: 0 + }]) + .toFile(actual) + .then(() => { + fixtures.assertMaxColourDistance(actual, expected); + }); + }); + it('multiple', () => { const filename = 'composite-multiple.png'; const actual = fixtures.path(`output.${filename}`); @@ -265,6 +324,12 @@ describe('composite', () => { }, /Expected boolean for tile but received invalid of type string/); }); + it('invalid premultiplied', () => { + assert.throws(() => { + sharp().composite([{ input: 'test', premultiplied: 'invalid' }]); + }, /Expected boolean for premultiplied but received invalid of type string/); + }); + it('invalid left', () => { assert.throws(() => { sharp().composite([{ input: 'test', left: 0.5 }]);