From 05dd191e17806b8ffd5eb0826c37995bccc8177b Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 21 Nov 2015 20:21:34 +0000 Subject: [PATCH] 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 --- src/pipeline.cc | 36 ++++++++++++++---- .../expected/flatten-rgb16-orange.jpg | Bin 0 -> 840 bytes test/fixtures/expected/svg.png | Bin 0 -> 5817 bytes test/fixtures/index.js | 1 + test/fixtures/tbgn2c16.png | Bin 0 -> 2041 bytes test/unit/alpha.js | 13 +++++++ test/unit/io.js | 5 ++- 7 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/expected/flatten-rgb16-orange.jpg create mode 100644 test/fixtures/expected/svg.png mode change 100755 => 100644 test/fixtures/index.js create mode 100644 test/fixtures/tbgn2c16.png mode change 100755 => 100644 test/unit/alpha.js mode change 100755 => 100644 test/unit/io.js 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 0000000000000000000000000000000000000000..91890f5a1364a06abd6efc0d63e3ec3ee9195132 GIT binary patch literal 840 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 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(); }); }); }