diff --git a/README.md b/README.md index ad6cfe7c..91dee556 100755 --- a/README.md +++ b/README.md @@ -392,6 +392,18 @@ Use PNG format for the output image. Use WebP format for the output image. +#### raw() + +_Requires libvips 7.42.0+_ + +Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output. + +The number of channels depends on the input image and selected options. + +* 1 channel for images converted to `greyscale()`, with each byte representing one pixel. +* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]). +* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\]. + #### quality(quality) The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`. diff --git a/index.js b/index.js index 772ceba1..9dd53743 100755 --- a/index.js +++ b/index.js @@ -341,10 +341,10 @@ Sharp.prototype.compressionLevel = function(compressionLevel) { }; /* - Disable the use of adaptive row filtering for PNG output - requires libvips 7.41.0+ + Disable the use of adaptive row filtering for PNG output - requires libvips 7.42.0+ */ Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) { - if (semver.gte(libvipsVersion, '7.41.0')) { + if (semver.gte(libvipsVersion, '7.42.0')) { this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true; } else { console.error('withoutAdaptiveFiltering requires libvips 7.41.0+'); @@ -425,6 +425,15 @@ Sharp.prototype.webp = function() { return this; }; +Sharp.prototype.raw = function() { + if (semver.gte(libvipsVersion, '7.42.0')) { + this.options.output = '__raw'; + } else { + console.error('Raw output requires libvips 7.42.0+'); + } + return this; +}; + /* Used by a Writable Stream to notify that it is ready for data */ diff --git a/src/resize.cc b/src/resize.cc index e6d322dc..0431d365 100755 --- a/src/resize.cc +++ b/src/resize.cc @@ -635,6 +635,35 @@ class ResizeWorker : public NanAsyncWorker { return Error(baton, hook); } baton->outputFormat = "webp"; +#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42) + } else if (baton->output == "__raw") { + // Write raw, uncompressed image data to buffer + if (baton->greyscale) { + // Extract first band for greyscale image + VipsImage *grey; + if (vips_extract_band(image, &grey, 1, NULL)) { + return Error(baton, hook); + } + vips_object_local(hook, grey); + image = grey; + } + if (image->BandFmt != VIPS_FORMAT_UCHAR) { + // Cast pixels to uint8 (unsigned char) + VipsImage *uchar; + if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, NULL)) { + return Error(baton, hook); + } + vips_object_local(hook, uchar); + image = uchar; + } + // Get raw image data + baton->bufferOut = vips_image_write_to_memory(image, &baton->bufferOutLength); + if (baton->bufferOut == NULL) { + (baton->err).append("Could not allocate enough memory for raw output"); + return Error(baton, hook); + } + baton->outputFormat = "raw"; +#endif } else { bool outputJpeg = IsJpeg(baton->output); bool outputPng = IsPng(baton->output); @@ -649,7 +678,7 @@ class ResizeWorker : public NanAsyncWorker { } baton->outputFormat = "jpeg"; } else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) { -#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41) +#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42) // Select PNG row filter int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL; // Write PNG to file diff --git a/test/unit/io.js b/test/unit/io.js index 75c22711..c531557e 100755 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -384,8 +384,8 @@ describe('Input/output', function() { done(); }); - if (semver.gte(sharp.libvipsVersion(), '7.41.0')) { - it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.41.0]', function(done) { + if (semver.gte(sharp.libvipsVersion(), '7.42.0')) { + it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function(done) { // First generate with adaptive filtering sharp(fixtures.inputPng) .resize(320, 240) @@ -463,4 +463,49 @@ describe('Input/output', function() { }); } + if (semver.gte(sharp.libvipsVersion(), '7.42.0')) { + describe('Ouput raw, uncompressed image data [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function() { + it('1 channel greyscale image', function(done) { + sharp(fixtures.inputJpg) + .greyscale() + .resize(32, 24) + .raw() + .toBuffer(function(err, data, info) { + assert.strictEqual(32 * 24 * 1, info.size); + assert.strictEqual(data.length, info.size); + assert.strictEqual('raw', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(24, info.height); + done(); + }); + }); + it('3 channel colour image without transparency', function(done) { + sharp(fixtures.inputJpg) + .resize(32, 24) + .raw() + .toBuffer(function(err, data, info) { + assert.strictEqual(32 * 24 * 3, info.size); + assert.strictEqual(data.length, info.size); + assert.strictEqual('raw', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(24, info.height); + done(); + }); + }); + it('4 channel colour image with transparency', function(done) { + sharp(fixtures.inputPngWithTransparency) + .resize(32, 24) + .raw() + .toBuffer(function(err, data, info) { + assert.strictEqual(32 * 24 * 4, info.size); + assert.strictEqual(data.length, info.size); + assert.strictEqual('raw', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(24, info.height); + done(); + }); + }); + }); + } + });