diff --git a/docs/api-colour.md b/docs/api-colour.md index 8598227b..2104fc4f 100644 --- a/docs/api-colour.md +++ b/docs/api-colour.md @@ -3,7 +3,7 @@ # background Set the background for the `embed`, `flatten` and `extend` operations. -The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency. +The default background is `{r: 0, g: 0, b: 0, alpha: 1}`, black without transparency. Delegates to the _color_ module, which can throw an Error but is liberal in what it accepts, clipping values to sensible min/max. diff --git a/docs/changelog.md b/docs/changelog.md index afbb4f8f..62fd5b7c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -37,6 +37,10 @@ Requires libvips v8.4.2. [#623](https://github.com/lovell/sharp/pull/623) [@ppaskaris](https://github.com/ppaskaris) +* Allow non-RGB input to embed/extend onto background with an alpha channel. + [#646](https://github.com/lovell/sharp/issues/646) + [@DaGaMs](https://github.com/DaGaMs) + ### v0.16 - "*pencil*" Requires libvips v8.3.3 diff --git a/lib/colour.js b/lib/colour.js index 65743f67..5bf92b4c 100644 --- a/lib/colour.js +++ b/lib/colour.js @@ -29,7 +29,12 @@ const colourspace = { */ const background = function background (rgba) { const colour = color(rgba); - this.options.background = colour.rgb().array().concat(colour.alpha() * 255); + this.options.background = [ + colour.red(), + colour.green(), + colour.blue(), + Math.round(colour.alpha() * 255) + ]; return this; }; diff --git a/package.json b/package.json index e4fd4d4f..3e7a1444 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ ], "dependencies": { "caw": "^2.0.0", - "color": "^1.0.1", + "color": "^1.0.2", "got": "^6.6.3", "nan": "^2.4.0", "semver": "^5.3.0", @@ -71,7 +71,7 @@ "async": "^2.1.4", "bufferutil": "^1.3.0", "cross-env": "^3.1.3", - "documentation": "^4.0.0-beta15", + "documentation": "^4.0.0-beta16", "exif-reader": "^1.0.1", "icc": "^0.0.2", "mocha": "^3.2.0", diff --git a/src/common.cc b/src/common.cc index 466f2569..61122ca4 100644 --- a/src/common.cc +++ b/src/common.cc @@ -434,4 +434,20 @@ namespace sharp { ); } + /* + Convert RGBA value to another colourspace + */ + std::vector GetRgbaAsColourspace(std::vector const rgba, VipsInterpretation const interpretation) { + int const bands = static_cast(rgba.size()); + if (bands < 3 || interpretation == VIPS_INTERPRETATION_sRGB || interpretation == VIPS_INTERPRETATION_RGB) { + return rgba; + } else { + VImage pixel = VImage::new_matrix(1, 1); + pixel.set("bands", bands); + pixel = pixel.new_from_image(rgba); + pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB)); + return pixel(0, 0); + } + } + } // namespace sharp diff --git a/src/common.h b/src/common.h index 35fbc65e..b21a290b 100644 --- a/src/common.h +++ b/src/common.h @@ -199,6 +199,11 @@ namespace sharp { */ VipsInterpretation GetInterpretation(std::string const typeStr); + /* + Convert RGBA value to another colourspace + */ + std::vector GetRgbaAsColourspace(std::vector const rgba, VipsInterpretation const interpretation); + } // namespace sharp #endif // SRC_COMMON_H_ diff --git a/src/pipeline.cc b/src/pipeline.cc index 61787406..3066e28d 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -470,6 +470,8 @@ class PipelineWorker : public Nan::AsyncWorker { if (baton->background[3] < 255.0 || HasAlpha(image)) { background.push_back(baton->background[3] * multiplier); } + // Ensure background colour uses correct colourspace + background = sharp::GetRgbaAsColourspace(background, image.interpretation()); // Add non-transparent alpha channel, if required if (baton->background[3] < 255.0 && !HasAlpha(image)) { image = image.bandjoin( @@ -538,6 +540,8 @@ class PipelineWorker : public Nan::AsyncWorker { if (baton->background[3] < 255.0 || HasAlpha(image)) { background.push_back(baton->background[3] * multiplier); } + // Ensure background colour uses correct colourspace + background = sharp::GetRgbaAsColourspace(background, image.interpretation()); // Add non-transparent alpha channel, if required if (baton->background[3] < 255.0 && !HasAlpha(image)) { image = image.bandjoin( @@ -689,14 +693,13 @@ class PipelineWorker : public Nan::AsyncWorker { } image = image.extract_band(baton->extractChannel); } - // Convert image to sRGB, if not already if (sharp::Is16Bit(image.interpretation())) { image = image.cast(VIPS_FORMAT_USHORT); } if (image.interpretation() != baton->colourspace) { - // Need to convert image - image = image.colourspace(baton->colourspace); + // Convert colourspace, pass the current known interpretation so libvips doesn't have to guess + image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); // Transform colours from embedded profile to output profile if (baton->withMetadata && sharp::HasProfile(image) && diff --git a/test/fixtures/cielab-dagams.tiff b/test/fixtures/cielab-dagams.tiff new file mode 100644 index 00000000..3a768025 Binary files /dev/null and b/test/fixtures/cielab-dagams.tiff differ diff --git a/test/fixtures/expected/embed-lab-into-rgba.png b/test/fixtures/expected/embed-lab-into-rgba.png new file mode 100644 index 00000000..23c5d9ae Binary files /dev/null and b/test/fixtures/expected/embed-lab-into-rgba.png differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 0dfb9933..d6b321e7 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -83,6 +83,7 @@ module.exports = { inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm + inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg diff --git a/test/unit/embed.js b/test/unit/embed.js index abf7fd06..62aef91b 100644 --- a/test/unit/embed.js +++ b/test/unit/embed.js @@ -22,24 +22,22 @@ describe('Embed', function () { }); }); - if (sharp.format.webp.output.buffer) { - it('JPEG within WebP, to include alpha channel', function (done) { - sharp(fixtures.inputJpg) - .resize(320, 240) - .background({r: 0, g: 0, b: 0, alpha: 0}) - .embed() - .webp() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('webp', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - assert.strictEqual(4, info.channels); - fixtures.assertSimilar(fixtures.expected('embed-3-into-4.webp'), data, done); - }); - }); - } + it('JPEG within WebP, to include alpha channel', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .embed() + .webp() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-3-into-4.webp'), data, done); + }); + }); it('PNG with alpha channel', function (done) { sharp(fixtures.inputPngWithTransparency) @@ -103,6 +101,23 @@ describe('Embed', function () { }); }); + it('embed TIFF in LAB colourspace onto RGBA background', function (done) { + sharp(fixtures.inputTiffCielab) + .resize(64, 128) + .embed() + .background({r: 255, g: 102, b: 0, alpha: 0.5}) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(64, info.width); + assert.strictEqual(128, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-lab-into-rgba.png'), data, done); + }); + }); + it('Enlarge and embed', function (done) { sharp(fixtures.inputPngWithOneColor) .embed()