diff --git a/src/pipeline.cc b/src/pipeline.cc index c1207f53..8aa001bb 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -437,17 +437,22 @@ class PipelineWorker : public AsyncWorker { 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 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 VipsArrayDouble *background = vips_array_double_newv( 3, // Ignore alpha channel as we're about to remove it - baton->background[0], - baton->background[1], - baton->background[2] + baton->background[0] * multiplier, + baton->background[1] * multiplier, + baton->background[2] * multiplier ); 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(background)); return Error(); } @@ -526,7 +531,7 @@ class PipelineWorker : public AsyncWorker { // See: http://entropymine.com/imageworsener/resizealpha/ if (shouldPremultiplyAlpha) { 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."); return Error(); } @@ -792,7 +797,7 @@ class PipelineWorker : public AsyncWorker { // Reverse premultiplication after all transformations: if (shouldPremultiplyAlpha) { 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"); return Error(); } @@ -820,7 +825,24 @@ class PipelineWorker : public AsyncWorker { } // 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 VipsImage *rgb; if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, nullptr)) { diff --git a/test/fixtures/expected/flatten-rgb16-orange.jpg b/test/fixtures/expected/flatten-rgb16-orange.jpg new file mode 100644 index 00000000..91890f5a Binary files /dev/null and b/test/fixtures/expected/flatten-rgb16-orange.jpg differ diff --git a/test/fixtures/expected/svg.png b/test/fixtures/expected/svg.png new file mode 100644 index 00000000..dccdde68 Binary files /dev/null and b/test/fixtures/expected/svg.png differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js old mode 100755 new mode 100644 index a17a8600..600aa225 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -71,6 +71,7 @@ module.exports = { inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'), inputPngWithOneColor: getPath('2x2_fdcce6.png'), + inputPngWithTransparency16bit: getPath('tbgn2c16.png'), // http://www.schaik.com/pngsuite/tbgn2c16.png inputPngOverlayLayer0: getPath('alpha-layer-0-background.png'), inputPngOverlayLayer1: getPath('alpha-layer-1-fill.png'), inputPngOverlayLayer2: getPath('alpha-layer-2-ink.png'), diff --git a/test/fixtures/tbgn2c16.png b/test/fixtures/tbgn2c16.png new file mode 100644 index 00000000..85cec395 Binary files /dev/null and b/test/fixtures/tbgn2c16.png differ diff --git a/test/unit/alpha.js b/test/unit/alpha.js old mode 100755 new mode 100644 index 41113cc4..ceeeb865 --- a/test/unit/alpha.js +++ b/test/unit/alpha.js @@ -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) { sharp(fixtures.inputPngWithTransparency) .flatten(false) diff --git a/test/unit/io.js b/test/unit/io.js old mode 100755 new mode 100644 index 52c35104..8ec88ff2 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -638,16 +638,17 @@ describe('Input/output', function() { sharp(fixtures.inputSvg) .resize(100, 100) .toFormat('png') - .toFile(fixtures.path('output.svg.png'), function(err, info) { + .toBuffer(function(err, data, info) { if (err) { assert.strictEqual(0, err.message.indexOf('Input file is of an unsupported image format')); + done(); } else { assert.strictEqual(true, info.size > 0); assert.strictEqual('png', info.format); assert.strictEqual(100, info.width); assert.strictEqual(100, info.height); + fixtures.assertSimilar(fixtures.expected('svg.png'), data, done); } - done(); }); }); }