mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Ensure background is premultiplied when compositing #2858
This commit is contained in:
parent
2031d7d112
commit
60adc110f5
@ -9,6 +9,9 @@ Requires libvips v8.11.3
|
|||||||
* Ensure correct PNG bitdepth is set based on number of colours.
|
* Ensure correct PNG bitdepth is set based on number of colours.
|
||||||
[#2855](https://github.com/lovell/sharp/issues/2855)
|
[#2855](https://github.com/lovell/sharp/issues/2855)
|
||||||
|
|
||||||
|
* Ensure background is always premultiplied when compositing.
|
||||||
|
[#2858](https://github.com/lovell/sharp/issues/2858)
|
||||||
|
|
||||||
### v0.29.0 - 17th August 2021
|
### v0.29.0 - 17th August 2021
|
||||||
|
|
||||||
* Drop support for Node.js 10, now requires Node.js >= 12.13.0.
|
* Drop support for Node.js 10, now requires Node.js >= 12.13.0.
|
||||||
|
@ -759,23 +759,27 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Convert RGBA value to another colourspace
|
Convert RGBA value to another colourspace
|
||||||
*/
|
*/
|
||||||
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation) {
|
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
||||||
|
VipsInterpretation const interpretation, bool premultiply) {
|
||||||
int const bands = static_cast<int>(rgba.size());
|
int const bands = static_cast<int>(rgba.size());
|
||||||
if (bands < 3 || interpretation == VIPS_INTERPRETATION_sRGB || interpretation == VIPS_INTERPRETATION_RGB) {
|
if (bands < 3) {
|
||||||
return rgba;
|
return rgba;
|
||||||
} else {
|
}
|
||||||
VImage pixel = VImage::new_matrix(1, 1);
|
VImage pixel = VImage::new_matrix(1, 1);
|
||||||
pixel.set("bands", bands);
|
pixel.set("bands", bands);
|
||||||
pixel = pixel.new_from_image(rgba);
|
pixel = pixel
|
||||||
pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
.new_from_image(rgba)
|
||||||
return pixel(0, 0);
|
.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
||||||
|
if (premultiply) {
|
||||||
|
pixel = pixel.premultiply();
|
||||||
}
|
}
|
||||||
|
return pixel(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Apply the alpha channel to a given colour
|
Apply the alpha channel to a given colour
|
||||||
*/
|
*/
|
||||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
|
||||||
// Scale up 8-bit values to match 16-bit input image
|
// Scale up 8-bit values to match 16-bit input image
|
||||||
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
// Create alphaColour colour
|
// Create alphaColour colour
|
||||||
@ -799,7 +803,7 @@ namespace sharp {
|
|||||||
alphaColour.push_back(colour[3] * multiplier);
|
alphaColour.push_back(colour[3] * multiplier);
|
||||||
}
|
}
|
||||||
// Ensure alphaColour colour uses correct colourspace
|
// Ensure alphaColour colour uses correct colourspace
|
||||||
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
|
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
|
||||||
// Add non-transparent alpha channel, if required
|
// Add non-transparent alpha channel, if required
|
||||||
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
||||||
image = image.bandjoin(
|
image = image.bandjoin(
|
||||||
|
@ -288,12 +288,13 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Convert RGBA value to another colourspace
|
Convert RGBA value to another colourspace
|
||||||
*/
|
*/
|
||||||
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
|
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
||||||
|
VipsInterpretation const interpretation, bool premultiply);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Apply the alpha channel to a given colour
|
Apply the alpha channel to a given colour
|
||||||
*/
|
*/
|
||||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Removes alpha channel, if any.
|
Removes alpha channel, if any.
|
||||||
|
@ -90,7 +90,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
if (baton->rotationAngle != 0.0) {
|
if (baton->rotationAngle != 0.0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
||||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,7 +423,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (image.width() != baton->width || image.height() != baton->height) {
|
if (image.width() != baton->width || image.height() != baton->height) {
|
||||||
if (baton->canvas == Canvas::EMBED) {
|
if (baton->canvas == Canvas::EMBED) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
||||||
|
|
||||||
// Embed
|
// Embed
|
||||||
|
|
||||||
@ -480,7 +480,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Rotate post-extract non-90 angle
|
// Rotate post-extract non-90 angle
|
||||||
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
||||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,7 +493,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Affine transform
|
// Affine transform
|
||||||
if (baton->affineMatrix.size() > 0) {
|
if (baton->affineMatrix.size() > 0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
||||||
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
||||||
->set("idx", baton->affineIdx)
|
->set("idx", baton->affineIdx)
|
||||||
->set("idy", baton->affineIdy)
|
->set("idy", baton->affineIdy)
|
||||||
@ -505,7 +505,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Extend edges
|
// Extend edges
|
||||||
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
||||||
|
|
||||||
// Embed
|
// Embed
|
||||||
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
||||||
|
@ -124,4 +124,30 @@ describe('Extend', function () {
|
|||||||
fixtures.assertSimilar(fixtures.expected('extend-2channel.png'), data, done);
|
fixtures.assertSimilar(fixtures.expected('extend-2channel.png'), data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Premultiply background when compositing', async () => {
|
||||||
|
const background = '#bf1942cc';
|
||||||
|
const data = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 1, height: 1, channels: 4, background: '#fff0'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.composite([{
|
||||||
|
input: {
|
||||||
|
create: {
|
||||||
|
width: 1, height: 1, channels: 4, background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
.extend({
|
||||||
|
left: 1, background
|
||||||
|
})
|
||||||
|
.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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user