Compare commits

..

15 Commits

Author SHA1 Message Date
Lovell Fuller
a15a9b956b Release v0.20.6 2018-08-20 11:40:10 +01:00
Lovell Fuller
42860c2f83 Changelog, credit and doc refresh for #1342 2018-08-19 10:43:25 +01:00
Alun Davies
b5b95e5ae1 Expose depth option for tile-based output (#1342) 2018-08-18 15:09:53 +01:00
Lovell Fuller
d705cffdd6 Ensure extractChannel works with 16-bit images #1330 2018-08-12 20:22:39 +01:00
Rodrigo Alviani
23a4bc103e Docs: correct quality option in overlayWith example (#1325) 2018-08-08 08:42:18 +01:00
Lovell Fuller
c14434f9e7 Add removeAlpha op, removes alpha channel if any #1248 2018-08-07 20:32:11 +01:00
Lovell Fuller
25bd2cea3e Add experimental entropy field to stats response 2018-08-06 15:41:27 +01:00
Lovell Fuller
532de4ecab Cache libvips binaries to reduce re-install time #1301 2018-08-05 10:31:41 +01:00
Lovell Fuller
bfdd27eeef Doc refresh and dependency bumps 2018-08-05 09:42:09 +01:00
Lovell Fuller
bd9f238ab4 Improve install time error messages for FreeBSD #1310 2018-08-04 22:27:32 +01:00
Lovell Fuller
75556bb57c Ensure vendor platform mismatch throws error #1303 2018-08-04 21:34:11 +01:00
thegareth
2de062a34a Docs: update the "make a transparent image" example (#1316)
Alpha for colour is between 0-1, not 0-255.
2018-08-02 09:42:25 +01:00
Lovell Fuller
4589b15dea Changelog and credit for #1285 #1290 2018-07-10 16:12:16 +01:00
Sylvain Dumont
8b75ce6786 Allow full WebP alphaQuality range of 0-100 (#1290) 2018-07-10 15:58:17 +01:00
Espen Hovlandsdal
7bbc5176a1 Expose mozjpeg quant_table flag (#1285) 2018-07-10 15:56:05 +01:00
28 changed files with 603 additions and 95 deletions

View File

@@ -1,5 +1,21 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## removeAlpha
Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
### Examples
```javascript
sharp('rgba.png')
.removeAlpha()
.toFile('rgb.png', function(err, info) {
// rgb.png is a 3 channel image without an alpha channel
});
```
Returns **Sharp**
## extractChannel
Extract a single channel from a multi-channel image.

View File

@@ -40,8 +40,7 @@ sharp('input.png')
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
.sharpen()
.withMetadata()
.quality(90)
.webp()
.webp( { quality: 90 } )
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains upside down, 300px wide, alpha channel flattened

View File

@@ -55,7 +55,7 @@ sharp({
width: 300,
height: 200,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 128 }
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
})
.png()

View File

@@ -79,6 +79,7 @@ A Promise is returned when `callback` is not provided.
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
### Parameters

View File

@@ -121,6 +121,8 @@ Use these JPEG options for output image.
- `options.optimizeScans` **[Boolean][6]** alternative spelling of optimiseScans (optional, default `false`)
- `options.optimiseCoding` **[Boolean][6]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[Boolean][6]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.quantisationTable` **[Number][8]** quantization table to use, integer 0-8, requires mozjpeg (optional, default `0`)
- `options.quantizationTable` **[Number][8]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[Boolean][6]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples
@@ -276,6 +278,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `tile.size` **[Number][8]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `tile.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `tile.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `tile.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)

View File

@@ -4,6 +4,37 @@
Requires libvips v8.6.1.
#### v0.20.6 - 20<sup>th</sup> August 2018
* Add removeAlpha operation to remove alpha channel, if any.
[#1248](https://github.com/lovell/sharp/issues/1248)
* Expose mozjpeg quant_table flag.
[#1285](https://github.com/lovell/sharp/pull/1285)
[@rexxars](https://github.com/rexxars)
* Allow full WebP alphaQuality range of 0-100.
[#1290](https://github.com/lovell/sharp/pull/1290)
[@sylvaindumont](https://github.com/sylvaindumont)
* Cache libvips binaries to reduce re-install time.
[#1301](https://github.com/lovell/sharp/issues/1301)
* Ensure vendor platform mismatch throws error at install time.
[#1303](https://github.com/lovell/sharp/issues/1303)
* Improve install time error messages for FreeBSD users.
[#1310](https://github.com/lovell/sharp/issues/1310)
* Ensure extractChannel works with 16-bit images.
[#1330](https://github.com/lovell/sharp/issues/1330)
* Expose depth option for tile-based output.
[#1342](https://github.com/lovell/sharp/pull/1342)
[@alundavies](https://github.com/alundavies)
* Add experimental entropy field to stats response.
#### v0.20.5 - 27<sup>th</sup> June 2018
* Expose libjpeg optimize_coding flag.

View File

@@ -113,6 +113,9 @@ the help and code contributions of the following people:
* [Thomas Parisot](https://github.com/oncletom)
* [Nathan Graves](https://github.com/woolite64)
* [Tom Lokhorst](https://github.com/tomlokhorst)
* [Espen Hovlandsdal](https://github.com/rexxars)
* [Sylvain Dumont](https://github.com/sylvaindumont)
* [Alun Davies](https://github.com/alundavies)
Thank you!

View File

@@ -17,6 +17,22 @@ const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const distBaseUrl = process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`;
const extractTarball = function (tarPath) {
const vendorPath = path.join(__dirname, '..', 'vendor');
if (!fs.existsSync(vendorPath)) {
fs.mkdirSync(vendorPath);
}
tar
.extract({
file: tarPath,
cwd: vendorPath,
strict: true
})
.catch(function (err) {
throw err;
});
};
try {
const useGlobalLibvips = libvips.useGlobalLibvips();
if (useGlobalLibvips) {
@@ -29,11 +45,15 @@ try {
} else {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
if (platform() === 'win32-ia32') {
const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}\n`);
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (platformAndArch === 'freebsd-x64') {
throw new Error(`FreeBSD systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (detectLibc.isNonGlibcLinux) {
throw new Error(`Use with ${detectLibc.family} libc requires manual installation of libvips >= ${minimumLibvipsVersion}`);
@@ -42,38 +62,30 @@ try {
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platform()].join('-') + '.tar.gz';
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) {
throw err;
}
if (response.statusCode !== 200) {
throw new Error(`Status ${response.statusCode}`);
}
response.pipe(tmpFile);
});
tmpFile.on('close', function () {
const vendorPath = path.join(__dirname, '..', 'vendor');
fs.mkdirSync(vendorPath);
tar
.extract({
file: tarPathTemp,
cwd: vendorPath,
strict: true
})
.then(function () {
try {
fs.unlinkSync(tarPathTemp);
} catch (err) {}
})
.catch(function (err) {
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz';
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`);
extractTarball(tarPathCache);
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) {
throw err;
});
});
}
if (response.statusCode !== 200) {
throw new Error(`Status ${response.statusCode}`);
}
response.pipe(tmpFile);
});
tmpFile.on('close', function () {
fs.renameSync(tarPathTemp, tarPathCache);
extractTarball(tarPathCache);
});
}
}
} catch (err) {
npmLog.error('sharp', err.message);

View File

@@ -12,6 +12,23 @@ const bool = {
eor: 'eor'
};
/**
* Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
*
* @example
* sharp('rgba.png')
* .removeAlpha()
* .toFile('rgb.png', function(err, info) {
* // rgb.png is a 3 channel image without an alpha channel
* });
*
* @returns {Sharp}
*/
function removeAlpha () {
this.options.removeAlpha = true;
return this;
}
/**
* Extract a single channel from a multi-channel image.
*
@@ -102,6 +119,7 @@ function bandbool (boolOp) {
module.exports = function (Sharp) {
// Public instance functions
[
removeAlpha,
extractChannel,
joinChannel,
bandbool

View File

@@ -19,8 +19,7 @@ const is = require('./is');
* .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
* .sharpen()
* .withMetadata()
* .quality(90)
* .webp()
* .webp( { quality: 90 } )
* .toBuffer()
* .then(function(outputBuffer) {
* // outputBuffer contains upside down, 300px wide, alpha channel flattened

View File

@@ -81,7 +81,7 @@ const debuglog = util.debuglog('sharp');
* width: 300,
* height: 200,
* channels: 4,
* background: { r: 255, g: 0, b: 0, alpha: 128 }
* background: { r: 255, g: 0, b: 0, alpha: 0.5 }
* }
* })
* .png()
@@ -171,6 +171,7 @@ const Sharp = function (input, options) {
booleanFileIn: '',
joinChannelIn: [],
extractChannel: -1,
removeAlpha: false,
colourspace: 'srgb',
// overlay
overlayGravity: 0,
@@ -193,6 +194,7 @@ const Sharp = function (input, options) {
jpegOvershootDeringing: false,
jpegOptimiseScans: false,
jpegOptimiseCoding: true,
jpegQuantisationTable: 0,
pngProgressive: false,
pngCompressionLevel: 9,
pngAdaptiveFiltering: false,

View File

@@ -264,6 +264,7 @@ function metadata (callback) {
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
*
* @example
* const image = sharp(inputJpg);

View File

@@ -1,17 +1,32 @@
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const spawnSync = require('child_process').spawnSync;
const semver = require('semver');
const platform = require('./platform');
const minimumLibvipsVersion = process.env.npm_package_config_libvips || require('../package.json').config.libvips;
const env = process.env;
const minimumLibvipsVersion = env.npm_package_config_libvips || require('../package.json').config.libvips;
const spawnSyncOptions = {
encoding: 'utf8',
shell: true
};
const cachePath = function () {
const npmCachePath = env.npm_config_cache || (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'));
if (!fs.existsSync(npmCachePath)) {
fs.mkdirSync(npmCachePath);
}
const libvipsCachePath = path.join(npmCachePath, '_libvips');
if (!fs.existsSync(libvipsCachePath)) {
fs.mkdirSync(libvipsCachePath);
}
return libvipsCachePath;
};
const globalLibvipsVersion = function () {
if (process.platform !== 'win32') {
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
@@ -23,21 +38,24 @@ const globalLibvipsVersion = function () {
const hasVendoredLibvips = function () {
const currentPlatformId = platform();
let vendorPlatformId;
try {
const vendorPlatformId = require(path.join(__dirname, '..', 'vendor', 'platform.json'));
vendorPlatformId = require(path.join(__dirname, '..', 'vendor', 'platform.json'));
} catch (err) {}
if (vendorPlatformId) {
if (currentPlatformId === vendorPlatformId) {
return true;
} else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
}
} catch (err) {}
}
return false;
};
const pkgConfigPath = function () {
if (process.platform !== 'win32') {
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
return [brewPkgConfigPath.trim(), process.env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
.filter(function (p) { return !!p; })
.join(':');
} else {
@@ -46,7 +64,7 @@ const pkgConfigPath = function () {
};
const useGlobalLibvips = function () {
if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return false;
}
@@ -56,6 +74,7 @@ const useGlobalLibvips = function () {
module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion,
cachePath: cachePath,
globalLibvipsVersion: globalLibvipsVersion,
hasVendoredLibvips: hasVendoredLibvips,
pkgConfigPath: pkgConfigPath,

View File

@@ -150,6 +150,8 @@ function withMetadata (withMetadata) {
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires mozjpeg
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -191,6 +193,14 @@ function jpeg (options) {
if (is.defined(options.optimiseCoding)) {
this._setBooleanOption('jpegOptimiseCoding', options.optimiseCoding);
}
options.quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
if (is.defined(options.quantisationTable)) {
if (is.integer(options.quantisationTable) && is.inRange(options.quantisationTable, 0, 8)) {
this.options.jpegQuantisationTable = options.quantisationTable;
} else {
throw new Error('Invalid quantisation table (integer, 0-8) ' + options.quantisationTable);
}
}
}
return this._updateFormatOut('jpeg', options);
}
@@ -261,10 +271,10 @@ function webp (options) {
}
}
if (is.object(options) && is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 1, 100)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
this.options.webpAlphaQuality = options.alphaQuality;
} else {
throw new Error('Invalid webp alpha quality (integer, 1-100) ' + options.alphaQuality);
throw new Error('Invalid webp alpha quality (integer, 0-100) ' + options.alphaQuality);
}
}
if (is.object(options) && is.defined(options.lossless)) {
@@ -413,6 +423,7 @@ function toFormat (format, options) {
* @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [tile.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String} [tile.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
* @returns {Sharp}
@@ -464,6 +475,15 @@ function tile (tile) {
throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + tile.angle);
}
}
// Depth of tiles
if (is.defined(tile.depth)) {
if (is.string(tile.depth) && is.inArray(tile.depth, ['onepixel', 'onetile', 'one'])) {
this.options.tileDepth = tile.depth;
} else {
throw new Error("Invalid tile depth '" + tile.depth + "', should be one of 'onepixel', 'onetile' or 'one'");
}
}
}
// Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.20.5",
"version": "0.20.6",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -49,7 +49,10 @@
"Rik Heywood <rik@rik.org>",
"Thomas Parisot <hi@oncletom.io>",
"Nathan Graves <nathanrgraves+github@gmail.com>",
"Tom Lokhorst <tom@lokhorst.eu>"
"Tom Lokhorst <tom@lokhorst.eu>",
"Espen Hovlandsdal <espen@hovlandsdal.com>",
"Sylvain Dumont <sylvain.dumont35@gmail.com>",
"Alun Davies <alun.owain.davies@googlemail.com>"
],
"scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
@@ -87,21 +90,21 @@
"fs-copy-file-sync": "^1.1.1",
"npmlog": "^4.1.2",
"prebuild-install": "^4.0.0",
"semver": "^5.5.0",
"semver": "^5.5.1",
"simple-get": "^2.8.1",
"tar": "^4.4.4",
"tar": "^4.4.6",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^2.6.1",
"cc": "^1.0.2",
"decompress-zip": "^0.3.1",
"documentation": "^8.0.0",
"documentation": "^8.1.1",
"exif-reader": "^1.0.2",
"icc": "^1.0.0",
"mocha": "^5.2.0",
"nyc": "^12.0.2",
"prebuild": "^7.6.0",
"prebuild": "^7.6.2",
"prebuild-ci": "^2.2.3",
"rimraf": "^2.6.2",
"semistandard": "^12.0.1"

View File

@@ -28,6 +28,16 @@ using vips::VError;
namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image) {
if (HasAlpha(image)) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
}
/*
Composite overlayImage over image at given position
Assumes alpha channels are already premultiplied and will be unpremultiplied after
@@ -223,10 +233,8 @@ namespace sharp {
VImage Gamma(VImage image, double const exponent) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
} else {
return image.gamma(VImage::option()->set("exponent", exponent));
}
@@ -374,10 +382,8 @@ namespace sharp {
VImage Linear(VImage image, double const a, double const b) {
if (HasAlpha(image)) {
// Separate alpha channel
VImage imageWithoutAlpha = image.extract_band(0,
VImage::option()->set("n", image.bands() - 1));
VImage alpha = image[image.bands() - 1];
return imageWithoutAlpha.linear(a, b).bandjoin(alpha);
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
} else {
return image.linear(a, b);
}

View File

@@ -25,6 +25,11 @@ using vips::VImage;
namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Alpha composite src over dst with given gravity.
Assumes alpha channels are already premultiplied and will be unpremultiplied after.

View File

@@ -694,10 +694,19 @@ class PipelineWorker : public Nan::AsyncWorker {
(baton->err).append("Cannot extract channel from image. Too few channels in image.");
return Error();
}
VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation())
? VIPS_INTERPRETATION_GREY16
: VIPS_INTERPRETATION_B_W;
image = image
.extract_band(baton->extractChannel)
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
.copy(VImage::option()->set("interpretation", interpretation));
}
// Remove alpha channel, if any
if (baton->removeAlpha) {
image = sharp::RemoveAlpha(image);
}
// Convert image to sRGB, if not already
if (sharp::Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT);
@@ -733,6 +742,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4")
->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing)
->set("optimize_scans", baton->jpegOptimiseScans)
->set("optimize_coding", baton->jpegOptimiseCoding)));
@@ -848,6 +858,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4")
->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing)
->set("optimize_scans", baton->jpegOptimiseScans)
->set("optimize_coding", baton->jpegOptimiseCoding));
@@ -927,6 +938,7 @@ class PipelineWorker : public Nan::AsyncWorker {
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
{"no_subsample", baton->jpegChromaSubsampling == "4:4:4" ? "TRUE": "FALSE"},
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
@@ -934,14 +946,22 @@ class PipelineWorker : public Nan::AsyncWorker {
suffix = AssembleSuffixString(extname, options);
}
// Write DZ to file
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle)));
vips::VOption *options = VImage::option()
->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle));
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
// not passing anything - libvips will handle choice
if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
options->set("depth", baton->tileDepth);
}
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) {
@@ -1232,6 +1252,7 @@ NAN_METHOD(pipeline) {
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
if (HasAttr(options, "boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));
@@ -1266,6 +1287,7 @@ NAN_METHOD(pipeline) {
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding");
@@ -1307,6 +1329,17 @@ NAN_METHOD(pipeline) {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
}
baton->tileFormat = AttrAsStr(options, "tileFormat");
std::string tileDepth = AttrAsStr(options, "tileDepth");
if (tileDepth == "onetile") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONETILE;
} else if (tileDepth == "one") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONE;
} else if (tileDepth == "onepixel") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL;
} else {
// signal that we do not want to pass any value to dzSave
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_LAST;
}
// Force random access for certain operations
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
baton->trimTolerance != 0 || baton->normalise ||

View File

@@ -102,6 +102,7 @@ struct PipelineBaton {
bool jpegProgressive;
std::string jpegChromaSubsampling;
bool jpegTrellisQuantisation;
int jpegQuantisationTable;
bool jpegOvershootDeringing;
bool jpegOptimiseScans;
bool jpegOptimiseCoding;
@@ -130,6 +131,7 @@ struct PipelineBaton {
VipsOperationBoolean booleanOp;
VipsOperationBoolean bandBoolOp;
int extractChannel;
bool removeAlpha;
VipsInterpretation colourspace;
int tileSize;
int tileOverlap;
@@ -137,6 +139,7 @@ struct PipelineBaton {
VipsForeignDzLayout tileLayout;
std::string tileFormat;
int tileAngle;
VipsForeignDzDepth tileDepth;
PipelineBaton():
input(nullptr),
@@ -188,6 +191,7 @@ struct PipelineBaton {
jpegProgressive(false),
jpegChromaSubsampling("4:2:0"),
jpegTrellisQuantisation(false),
jpegQuantisationTable(0),
jpegOvershootDeringing(false),
jpegOptimiseScans(false),
jpegOptimiseCoding(true),
@@ -211,12 +215,14 @@ struct PipelineBaton {
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
removeAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST),
tileSize(256),
tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
tileAngle(0){
tileAngle(0),
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST){
background[0] = 0.0;
background[1] = 0.0;
background[2] = 0.0;

View File

@@ -59,7 +59,6 @@ class StatsWorker : public Nan::AsyncWorker {
using sharp::MaximumImageAlpha;
vips::VImage image;
vips::VImage stats;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
@@ -69,9 +68,8 @@ class StatsWorker : public Nan::AsyncWorker {
}
if (imageType != sharp::ImageType::UNKNOWN) {
try {
stats = image.stats();
int bands = image.bands();
double const max = MaximumImageAlpha(image.interpretation());
vips::VImage stats = image.stats();
int const bands = image.bands();
for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
@@ -83,11 +81,15 @@ class StatsWorker : public Nan::AsyncWorker {
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
baton->channelStats.push_back(cStats);
}
// alpha layer is there and the last band i.e. alpha has its max value greater than 0)
if (sharp::HasAlpha(image) && stats.getpoint(STAT_MIN_INDEX, bands).front() != max) {
baton->isOpaque = false;
// Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false;
}
}
// Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(image.colourspace(VIPS_INTERPRETATION_B_W)[0].hist_find().hist_entropy());
} catch (vips::VError const &err) {
(baton->err).append(err.what());
}
@@ -130,6 +132,7 @@ class StatsWorker : public Nan::AsyncWorker {
Set(info, New("channels").ToLocalChecked(), channels);
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
argv[1] = info;
}

View File

@@ -51,12 +51,14 @@ struct StatsBaton {
// Output
std::vector<ChannelStats> channelStats;
bool isOpaque;
double entropy;
std::string err;
StatsBaton():
input(nullptr),
isOpaque(true)
isOpaque(true),
entropy(0.0)
{}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

View File

@@ -81,35 +81,45 @@ describe('Alpha transparency', function () {
});
});
it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function (done) {
it('Enlargement with non-nearest neighbor interpolation shouldnt cause dark edges', function () {
const base = 'alpha-premultiply-enlargement-2048x1536-paper.png';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
return sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.resize(2048, 1536)
.toFile(actual, function (err) {
if (err) {
done(err);
} else {
fixtures.assertMaxColourDistance(actual, expected, 102);
done();
}
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 102);
});
});
it('Reduction with non-nearest neighbor interpolation shouldnt cause dark edges', function (done) {
it('Reduction with non-nearest neighbor interpolation shouldnt cause dark edges', function () {
const base = 'alpha-premultiply-reduction-1024x768-paper.png';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
sharp(fixtures.inputPngAlphaPremultiplicationLarge)
return sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(1024, 768)
.toFile(actual, function (err) {
if (err) {
done(err);
} else {
fixtures.assertMaxColourDistance(actual, expected, 102);
done();
}
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 102);
});
});
it('Removes alpha from fixtures with transparency, ignores those without', function () {
return Promise.all([
fixtures.inputPngWithTransparency,
fixtures.inputPngWithTransparency16bit,
fixtures.inputWebPWithTransparency,
fixtures.inputJpg,
fixtures.inputPng,
fixtures.inputWebP
].map(function (input) {
return sharp(input)
.removeAlpha()
.toBuffer({ resolveWithObject: true })
.then(function (result) {
assert.strictEqual(3, result.info.channels);
});
}));
});
});

View File

@@ -69,6 +69,17 @@ describe('Image channel extraction', function () {
});
});
it('Alpha from 16-bit PNG', function (done) {
const output = fixtures.path('output.extract-alpha-16bit.jpg');
sharp(fixtures.inputPngWithTransparency16bit)
.extractChannel(3)
.toFile(output, function (err, info) {
if (err) throw err;
fixtures.assertMaxColourDistance(output, fixtures.expected('extract-alpha-16bit.jpg'));
done();
});
});
it('Invalid channel number', function () {
assert.throws(function () {
sharp(fixtures.inputJpg)

View File

@@ -389,6 +389,16 @@ describe('Input/output', function () {
});
});
describe('Invalid JPEG quantisation table', function () {
[-1, 88.2, 'test'].forEach(function (table) {
it(table.toString(), function () {
assert.throws(function () {
sharp().jpeg({ quantisationTable: table });
});
});
});
});
it('Progressive JPEG image', function (done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
@@ -856,6 +866,37 @@ describe('Input/output', function () {
});
});
it('Specifying quantisation table provides different JPEG', function (done) {
// First generate with default quantisation table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false })
.toBuffer(function (err, withDefaultQuantisationTable, withInfo) {
if (err) throw err;
assert.strictEqual(true, withDefaultQuantisationTable.length > 0);
assert.strictEqual(withDefaultQuantisationTable.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Then generate with different quantisation table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false, quantisationTable: 3 })
.toBuffer(function (err, withQuantTable3, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withQuantTable3.length > 0);
assert.strictEqual(withQuantTable3.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantisationTable.length);
done();
});
});
});
it('Convert SVG to PNG at default 72DPI', function (done) {
sharp(fixtures.inputSvg)
.resize(1024)

View File

@@ -1,6 +1,7 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const semver = require('semver');
const libvips = require('../../lib/libvips');
@@ -66,5 +67,11 @@ describe('libvips binaries', function () {
delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS;
});
it('cachePath returns a valid path ending with _libvips', function () {
const cachePath = libvips.cachePath();
assert.strictEqual('string', typeof cachePath);
assert.strictEqual('_libvips', cachePath.substr(-8));
assert.strictEqual(true, fs.existsSync(cachePath));
});
});
});

View File

@@ -24,6 +24,7 @@ describe('Image Stats', function () {
if (err) throw err;
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -82,6 +83,7 @@ describe('Image Stats', function () {
if (err) throw err;
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -105,7 +107,9 @@ describe('Image Stats', function () {
it('PNG with transparency', function (done) {
sharp(fixtures.inputPngWithTransparency).stats(function (err, stats) {
if (err) throw err;
assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -180,6 +184,7 @@ describe('Image Stats', function () {
if (err) throw err;
assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));
// alpha channel
assert.strictEqual(0, stats.channels[3]['min']);
@@ -204,7 +209,9 @@ describe('Image Stats', function () {
it('Tiff', function (done) {
sharp(fixtures.inputTiff).stats(function (err, stats) {
if (err) throw err;
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -231,6 +238,7 @@ describe('Image Stats', function () {
if (err) throw err;
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -289,6 +297,7 @@ describe('Image Stats', function () {
if (err) throw err;
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));
// red channel
assert.strictEqual(35, stats.channels[0]['min']);
@@ -345,7 +354,9 @@ describe('Image Stats', function () {
it('Grayscale GIF with alpha', function (done) {
sharp(fixtures.inputGifGreyPlusAlpha).stats(function (err, stats) {
if (err) throw err;
assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));
// gray channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -387,7 +398,9 @@ describe('Image Stats', function () {
const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().stats(function (err, stats) {
if (err) throw err;
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -449,6 +462,7 @@ describe('Image Stats', function () {
return pipeline.stats().then(function (stats) {
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);
@@ -505,6 +519,7 @@ describe('Image Stats', function () {
it('File in, Promise out', function () {
return sharp(fixtures.inputJpg).stats().then(function (stats) {
assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
// red channel
assert.strictEqual(0, stats.channels[0]['min']);

View File

@@ -46,6 +46,51 @@ const assertDeepZoomTiles = function (directory, expectedSize, expectedLevels, d
}, done);
};
const assertZoomifyTiles = function (directory, expectedTileSize, expectedLevels, done) {
fs.stat(path.join(directory, 'ImageProperties.xml'), function (err, stat) {
if (err) throw err;
assert.ok(stat.isFile());
assert.ok(stat.size > 0);
let maxTileLevel = -1;
fs.readdirSync(path.join(directory, 'TileGroup0')).forEach(function (tile) {
// Verify tile file name
assert.ok(/^[0-9]+-[0-9]+-[0-9]+\.jpg$/.test(tile));
let level = parseInt(tile.split('-')[0]);
maxTileLevel = Math.max(maxTileLevel, level);
});
assert.strictEqual(maxTileLevel + 1, expectedLevels); // add one to account for zero level tile
done();
});
};
const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels, done) {
const levels = fs.readdirSync(directory);
assert.strictEqual(expectedLevels, levels.length - 1); // subtract one to account for default blank tile
fs.stat(path.join(directory, 'blank.png'), function (err, stat) {
if (err) throw err;
assert.ok(stat.isFile());
assert.ok(stat.size > 0);
// Basic check to confirm lowest and highest level tiles exist
fs.stat(path.join(directory, '0', '0', '0.jpg'), function (err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
fs.stat(path.join(directory, (expectedLevels - 1).toString(), '0', '0.jpg'), function (err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
done();
});
});
});
};
describe('Tile', function () {
it('Valid size values pass', function () {
[1, 8192].forEach(function (size) {
@@ -144,6 +189,26 @@ describe('Tile', function () {
});
});
it('Valid depths pass', function () {
['onepixel', 'onetile', 'one'].forEach(function (depth) {
assert.doesNotThrow(function (depth) {
sharp().tile({
depth: depth
});
});
});
});
it('Invalid depths fail', function () {
['depth', 1].forEach(function (depth) {
assert.throws(function () {
sharp().tile({
depth: depth
});
});
});
});
it('Prevent larger overlap than default size', function () {
assert.throws(function () {
sharp().tile({
@@ -251,6 +316,54 @@ describe('Tile', function () {
});
});
it('Deep Zoom layout with depth of one', function (done) {
const directory = fixtures.path('output.512_depth_one.dzi_files');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 512,
depth: 'one'
})
.toFile(fixtures.path('output.512_depth_one.dzi'), function (err, info) {
if (err) throw err;
// Verify only one depth generated
assertDeepZoomTiles(directory, 512, 1, done);
});
});
});
it('Deep Zoom layout with depth of onepixel', function (done) {
const directory = fixtures.path('output.512_depth_onepixel.dzi_files');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 512,
depth: 'onepixel'
})
.toFile(fixtures.path('output.512_depth_onepixel.dzi'), function (err, info) {
if (err) throw err;
// Verify only one depth generated
assertDeepZoomTiles(directory, 512, 13, done);
});
});
});
it('Deep Zoom layout with depth of onetile', function (done) {
const directory = fixtures.path('output.256_depth_onetile.dzi_files');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 256,
depth: 'onetile'
})
.toFile(fixtures.path('output.256_depth_onetile.dzi'), function (err, info) {
if (err) throw err;
// Verify only one depth generated
assertDeepZoomTiles(directory, 256, 5, done);
});
});
});
it('Zoomify layout', function (done) {
const directory = fixtures.path('output.zoomify.dzi');
rimraf(directory, function () {
@@ -275,6 +388,69 @@ describe('Tile', function () {
});
});
it('Zoomify layout with depth one', function (done) {
const directory = fixtures.path('output.zoomify.depth_one.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 256,
layout: 'zoomify',
depth: 'one'
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertZoomifyTiles(directory, 256, 1, done);
});
});
});
it('Zoomify layout with depth onetile', function (done) {
const directory = fixtures.path('output.zoomify.depth_onetile.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 256,
layout: 'zoomify',
depth: 'onetile'
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertZoomifyTiles(directory, 256, 5, done);
});
});
});
it('Zoomify layout with depth onepixel', function (done) {
const directory = fixtures.path('output.zoomify.depth_onepixel.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 256,
layout: 'zoomify',
depth: 'onepixel'
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertZoomifyTiles(directory, 256, 13, done);
});
});
});
it('Google layout', function (done) {
const directory = fixtures.path('output.google.dzi');
rimraf(directory, function () {
@@ -410,6 +586,72 @@ describe('Tile', function () {
});
});
it('Google layout with depth one', function (done) {
const directory = fixtures.path('output.google_depth_one.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
layout: 'google',
depth: 'one',
size: 256
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertGoogleTiles(directory, 256, 1, done);
});
});
});
it('Google layout with depth onepixel', function (done) {
const directory = fixtures.path('output.google_depth_onepixel.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
layout: 'google',
depth: 'onepixel',
size: 256
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertGoogleTiles(directory, 256, 13, done);
});
});
});
it('Google layout with depth onetile', function (done) {
const directory = fixtures.path('output.google_depth_onetile.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
layout: 'google',
depth: 'onetile',
size: 256
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertGoogleTiles(directory, 256, 5, done);
});
});
});
it('Write to ZIP container using file extension', function (done) {
const container = fixtures.path('output.dz.container.zip');
const extractTo = fixtures.path('output.dz.container');