Add premultiplied boolean flag for raw pixel data input (#2685)

This commit is contained in:
Michael Nutt 2021-05-03 14:30:37 -04:00 committed by GitHub
parent 309918a878
commit 9a1e8ed574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 60 additions and 1 deletions

View File

@ -37,6 +37,8 @@ Implements the [stream.Duplex][1] class.
* `options.raw.width` **[number][8]?** integral number of pixels wide. * `options.raw.width` **[number][8]?** integral number of pixels wide.
* `options.raw.height` **[number][8]?** integral number of pixels high. * `options.raw.height` **[number][8]?** integral number of pixels high.
* `options.raw.channels` **[number][8]?** integral number of channels, between 1 and 4. * `options.raw.channels` **[number][8]?** integral number of channels, between 1 and 4.
* `options.raw.premultiplied` **[boolean][7]?** specifies that the raw input has already been premultiplied, set to `true`
to avoid sharp premultiplying the image. (optional, default `false`)
* `options.create` **[Object][6]?** describes a new image to be created. * `options.create` **[Object][6]?** describes a new image to be created.
* `options.create.width` **[number][8]?** integral number of pixels wide. * `options.create.width` **[number][8]?** integral number of pixels wide.

View File

@ -139,6 +139,8 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.raw.width] - integral number of pixels wide. * @param {number} [options.raw.width] - integral number of pixels wide.
* @param {number} [options.raw.height] - integral number of pixels high. * @param {number} [options.raw.height] - integral number of pixels high.
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4. * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
* to avoid sharp premultiplying the image. (optional, default `false`)
* @param {Object} [options.create] - describes a new image to be created. * @param {Object} [options.create] - describes a new image to be created.
* @param {number} [options.create.width] - integral number of pixels wide. * @param {number} [options.create.width] - integral number of pixels wide.
* @param {number} [options.create.height] - integral number of pixels high. * @param {number} [options.create.height] - integral number of pixels high.

View File

@ -103,6 +103,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.rawWidth = inputOptions.raw.width; inputDescriptor.rawWidth = inputOptions.raw.width;
inputDescriptor.rawHeight = inputOptions.raw.height; inputDescriptor.rawHeight = inputOptions.raw.height;
inputDescriptor.rawChannels = inputOptions.raw.channels; inputDescriptor.rawChannels = inputOptions.raw.channels;
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
} else { } else {
throw new Error('Expected width, height and channels for raw pixel input'); throw new Error('Expected width, height and channels for raw pixel input');
} }

View File

@ -75,7 +75,8 @@
"Manan Jadhav <manan@motionden.com>", "Manan Jadhav <manan@motionden.com>",
"Leon Radley <leon@radley.se>", "Leon Radley <leon@radley.se>",
"alza54 <alza54@thiocod.in>", "alza54 <alza54@thiocod.in>",
"Jacob Smith <jacob@frende.me>" "Jacob Smith <jacob@frende.me>",
"Michael Nutt <michael@nutt.im>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",

View File

@ -95,6 +95,7 @@ namespace sharp {
descriptor->rawChannels = AttrAsUint32(input, "rawChannels"); descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrAsUint32(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrAsUint32(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
@ -302,6 +303,9 @@ namespace sharp {
} else { } else {
image.get_image()->Type = VIPS_INTERPRETATION_sRGB; image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
} }
if (descriptor->rawPremultiplied) {
image = image.unpremultiply();
}
imageType = ImageType::RAW; imageType = ImageType::RAW;
} else { } else {
// Compressed data // Compressed data

View File

@ -57,6 +57,7 @@ namespace sharp {
int rawChannels; int rawChannels;
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
bool rawPremultiplied;
int pages; int pages;
int page; int page;
int level; int level;
@ -80,6 +81,7 @@ namespace sharp {
rawChannels(0), rawChannels(0),
rawWidth(0), rawWidth(0),
rawHeight(0), rawHeight(0),
rawPremultiplied(false),
pages(1), pages(1),
page(0), page(0),
level(0), level(0),

View File

@ -90,6 +90,7 @@ module.exports = {
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0 inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg) inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597 inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp 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 inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

BIN
test/fixtures/with-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -107,6 +107,52 @@ describe('Raw pixel data', function () {
}); });
}); });
it('RGBA premultiplied', function (done) {
// Convert to raw pixel data
sharp(fixtures.inputPngSolidAlpha)
.resize(256)
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(256, info.width);
assert.strictEqual(192, info.height);
assert.strictEqual(4, info.channels);
const originalData = Buffer.from(data);
// Premultiply image data
for (let i = 0; i < data.length; i += 4) {
const alpha = data[i + 3];
const norm = alpha / 255;
if (alpha < 255) {
data[i] = Math.round(data[i] * norm);
data[i + 1] = Math.round(data[i + 1] * norm);
data[i + 2] = Math.round(data[i + 2] * norm);
}
}
// Convert back to PNG
sharp(data, {
raw: {
width: info.width,
height: info.height,
channels: info.channels,
premultiplied: true
}
})
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(256, info.width);
assert.strictEqual(192, info.height);
assert.strictEqual(4, info.channels);
assert.equal(data.compare(originalData), 0, 'output buffer matches unpremultiplied input buffer');
done();
});
});
});
it('JPEG to raw Stream and back again', function (done) { it('JPEG to raw Stream and back again', function (done) {
const width = 32; const width = 32;
const height = 24; const height = 24;