Ensure 16-bit+alpha input images work with vips_premultiply #301

Improves SVG support as *magick serves these as 16-bit

Add automated tests for SVG and 16-bit+alpha PNG inputs
This commit is contained in:
Lovell Fuller 2015-11-21 20:21:34 +00:00
parent c9ecc7a517
commit 05dd191e17
7 changed files with 46 additions and 9 deletions

View File

@ -437,17 +437,22 @@ class PipelineWorker : public AsyncWorker {
image = transformed; image = transformed;
} }
// Calculate maximum alpha value based on input image pixel depth
double maxAlpha = (image->BandFmt == VIPS_FORMAT_USHORT) ? 65535.0 : 255.0;
// Flatten image to remove alpha channel // Flatten image to remove alpha channel
if (baton->flatten && HasAlpha(image)) { if (baton->flatten && HasAlpha(image)) {
// Scale up 8-bit values to match 16-bit input image
double multiplier = (image->Type == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
// Background colour // Background colour
VipsArrayDouble *background = vips_array_double_newv( VipsArrayDouble *background = vips_array_double_newv(
3, // Ignore alpha channel as we're about to remove it 3, // Ignore alpha channel as we're about to remove it
baton->background[0], baton->background[0] * multiplier,
baton->background[1], baton->background[1] * multiplier,
baton->background[2] baton->background[2] * multiplier
); );
VipsImage *flattened; VipsImage *flattened;
if (vips_flatten(image, &flattened, "background", background, nullptr)) { if (vips_flatten(image, &flattened, "background", background, "max_alpha", maxAlpha, nullptr)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background)); vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error(); return Error();
} }
@ -526,7 +531,7 @@ class PipelineWorker : public AsyncWorker {
// See: http://entropymine.com/imageworsener/resizealpha/ // See: http://entropymine.com/imageworsener/resizealpha/
if (shouldPremultiplyAlpha) { if (shouldPremultiplyAlpha) {
VipsImage *imagePremultiplied; VipsImage *imagePremultiplied;
if (vips_premultiply(image, &imagePremultiplied, nullptr)) { if (vips_premultiply(image, &imagePremultiplied, "max_alpha", maxAlpha, nullptr)) {
(baton->err).append("Failed to premultiply alpha channel."); (baton->err).append("Failed to premultiply alpha channel.");
return Error(); return Error();
} }
@ -792,7 +797,7 @@ class PipelineWorker : public AsyncWorker {
// Reverse premultiplication after all transformations: // Reverse premultiplication after all transformations:
if (shouldPremultiplyAlpha) { if (shouldPremultiplyAlpha) {
VipsImage *imageUnpremultiplied; VipsImage *imageUnpremultiplied;
if (vips_unpremultiply(image, &imageUnpremultiplied, nullptr)) { if (vips_unpremultiply(image, &imageUnpremultiplied, "max_alpha", maxAlpha, nullptr)) {
(baton->err).append("Failed to unpremultiply alpha channel"); (baton->err).append("Failed to unpremultiply alpha channel");
return Error(); return Error();
} }
@ -820,7 +825,24 @@ class PipelineWorker : public AsyncWorker {
} }
// Convert image to sRGB, if not already // Convert image to sRGB, if not already
if (image->Type != VIPS_INTERPRETATION_sRGB) { if (image->Type == VIPS_INTERPRETATION_RGB16) {
// Ensure 16-bit integer
VipsImage *ushort;
if (vips_cast_ushort(image, &ushort, nullptr)) {
return Error();
}
vips_object_local(hook, ushort);
image = ushort;
// Fast conversion to 8-bit integer by discarding least-significant byte
VipsImage *msb;
if (vips_msb(image, &msb, nullptr)) {
return Error();
}
vips_object_local(hook, msb);
image = msb;
// Explicitly set to sRGB
image->Type = VIPS_INTERPRETATION_sRGB;
} else if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Switch interpretation to sRGB // Switch interpretation to sRGB
VipsImage *rgb; VipsImage *rgb;
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, nullptr)) { if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, nullptr)) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

BIN
test/fixtures/expected/svg.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

1
test/fixtures/index.js vendored Executable file → Normal file
View File

@ -71,6 +71,7 @@ module.exports = {
inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'), inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
inputPngWithOneColor: getPath('2x2_fdcce6.png'), inputPngWithOneColor: getPath('2x2_fdcce6.png'),
inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png
inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'), inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'),
inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'), inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'),
inputPngOverlayLayer2: getPath('alpha-layer-2-ink.png'), inputPngOverlayLayer2: getPath('alpha-layer-2-ink.png'),

BIN
test/fixtures/tbgn2c16.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

13
test/unit/alpha.js Executable file → Normal file
View File

@ -46,6 +46,19 @@ describe('Alpha transparency', function() {
}); });
}); });
it('Flatten 16-bit PNG with transparency to orange', function(done) {
sharp(fixtures.inputPngWithTransparency16bit)
.flatten()
.background({r: 255, g: 102, b: 0})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual(32, info.width);
assert.strictEqual(32, info.height);
fixtures.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, done);
});
});
it('Do not flatten', function(done) { it('Do not flatten', function(done) {
sharp(fixtures.inputPngWithTransparency) sharp(fixtures.inputPngWithTransparency)
.flatten(false) .flatten(false)

5
test/unit/io.js Executable file → Normal file
View File

@ -638,16 +638,17 @@ describe('Input/output', function() {
sharp(fixtures.inputSvg) sharp(fixtures.inputSvg)
.resize(100, 100) .resize(100, 100)
.toFormat('png') .toFormat('png')
.toFile(fixtures.path('output.svg.png'), function(err, info) { .toBuffer(function(err, data, info) {
if (err) { if (err) {
assert.strictEqual(0, err.message.indexOf('Input file is of an unsupported image format')); assert.strictEqual(0, err.message.indexOf('Input file is of an unsupported image format'));
done();
} else { } else {
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width); assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height); assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('svg.png'), data, done);
} }
done();
}); });
}); });
} }