mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Allow PNG and WebP tile-based output in addition to JPEG (#622)
This commit is contained in:
parent
6b426014ad
commit
bc84d1e47a
@ -285,7 +285,13 @@ const tile = function tile (tile) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
// Format
|
||||
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
||||
this.options.tileFormat = this.options.formatOut;
|
||||
} else if (this.options.formatOut !== 'input') {
|
||||
throw new Error('Invalid tile format ' + this.options.formatOut);
|
||||
}
|
||||
return this._updateFormatOut('dz');
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,8 @@
|
||||
"F. Orlando Galashan <frulo@gmx.de>",
|
||||
"Kleis Auke Wolthuizen <info@kleisauke.nl>",
|
||||
"Matt Hirsch <mhirsch@media.mit.edu>",
|
||||
"Matthias Thoemmes <thoemmes@gmail.com>"
|
||||
"Matthias Thoemmes <thoemmes@gmail.com>",
|
||||
"Patrick Paskaris <patrick@paskaris.gr>"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*",
|
||||
|
@ -846,6 +846,35 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
if (isDzZip) {
|
||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||
}
|
||||
// Forward format options through suffix
|
||||
std::string suffix;
|
||||
if (baton->tileFormat == "png") {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
|
||||
{"compression", std::to_string(baton->pngCompressionLevel)},
|
||||
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
||||
};
|
||||
suffix = AssembleSuffixString(".png", options);
|
||||
} else if (baton->tileFormat == "webp") {
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"Q", std::to_string(baton->webpQuality)}
|
||||
};
|
||||
suffix = AssembleSuffixString(".webp", options);
|
||||
} else {
|
||||
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE
|
||||
|| baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY
|
||||
? ".jpg" : ".jpeg";
|
||||
std::vector<std::pair<std::string, std::string>> options {
|
||||
{"Q", std::to_string(baton->jpegQuality)},
|
||||
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
|
||||
{"no_subsample", baton->jpegChromaSubsampling == "4:4:4" ? "TRUE": "FALSE"},
|
||||
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
|
||||
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
|
||||
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
|
||||
{"optimize_coding", "TRUE"}
|
||||
};
|
||||
suffix = AssembleSuffixString(extname, options);
|
||||
}
|
||||
// Write DZ to file
|
||||
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
@ -853,6 +882,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("overlap", baton->tileOverlap)
|
||||
->set("container", baton->tileContainer)
|
||||
->set("layout", baton->tileLayout)
|
||||
->set("suffix", const_cast<char*>(suffix.data()))
|
||||
);
|
||||
baton->formatOut = "dz";
|
||||
} else if (baton->formatOut == "v" || isV || (matchInput && inputImageType == ImageType::VIPS)) {
|
||||
@ -990,6 +1020,23 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
return std::make_tuple(rotate, flip, flop);
|
||||
}
|
||||
|
||||
/*
|
||||
Assemble the suffix argument to dzsave, which is the format (by extname)
|
||||
alongisde comma-separated arguments to the corresponding `formatsave` vips
|
||||
action.
|
||||
*/
|
||||
std::string
|
||||
AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) {
|
||||
std::string argument;
|
||||
for (auto const &option : options) {
|
||||
if (!argument.empty()) {
|
||||
argument += ",";
|
||||
}
|
||||
argument += option.first + "=" + option.second;
|
||||
}
|
||||
return extname + "[" + argument + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
Clear all thread-local data.
|
||||
*/
|
||||
@ -1167,6 +1214,7 @@ NAN_METHOD(pipeline) {
|
||||
} else {
|
||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
|
||||
}
|
||||
baton->tileFormat = AttrAsStr(options, "tileFormat");
|
||||
|
||||
// Function to notify of queue length changes
|
||||
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener"));
|
||||
|
@ -102,6 +102,7 @@ struct PipelineBaton {
|
||||
int tileOverlap;
|
||||
VipsForeignDzContainer tileContainer;
|
||||
VipsForeignDzLayout tileLayout;
|
||||
std::string tileFormat;
|
||||
|
||||
PipelineBaton():
|
||||
input(nullptr),
|
||||
|
@ -128,6 +128,22 @@ describe('Tile', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Valid formats pass', function () {
|
||||
['jpeg', 'png', 'webp'].forEach(function (format) {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().toFormat(format).tile();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid formats fail', function () {
|
||||
['tiff', 'raw'].forEach(function (format) {
|
||||
assert.throws(function () {
|
||||
sharp().toFormat(format).tile();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Prevent larger overlap than default size', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tile({overlap: 257});
|
||||
@ -224,6 +240,111 @@ describe('Tile', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Google layout with jpeg format', function (done) {
|
||||
const directory = fixtures.path('output.jpg.google.dzi');
|
||||
rimraf(directory, function () {
|
||||
sharp(fixtures.inputJpg)
|
||||
.jpeg({ quality: 1 })
|
||||
.tile({
|
||||
layout: 'google'
|
||||
})
|
||||
.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);
|
||||
const sample = path.join(directory, '0', '0', '0.jpg');
|
||||
sharp(sample).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasProfile);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
assert.strictEqual(true, metadata.width === 256);
|
||||
assert.strictEqual(true, metadata.height === 256);
|
||||
fs.stat(sample, function (err, stat) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.size < 2000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Google layout with png format', function (done) {
|
||||
const directory = fixtures.path('output.png.google.dzi');
|
||||
rimraf(directory, function () {
|
||||
sharp(fixtures.inputJpg)
|
||||
.png({ compressionLevel: 1 })
|
||||
.tile({
|
||||
layout: 'google'
|
||||
})
|
||||
.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);
|
||||
const sample = path.join(directory, '0', '0', '0.png');
|
||||
sharp(sample).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', metadata.format);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasProfile);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
assert.strictEqual(true, metadata.width === 256);
|
||||
assert.strictEqual(true, metadata.height === 256);
|
||||
fs.stat(sample, function (err, stat) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.size > 44000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Google layout with webp format', function (done) {
|
||||
const directory = fixtures.path('output.webp.google.dzi');
|
||||
rimraf(directory, function () {
|
||||
sharp(fixtures.inputJpg)
|
||||
.webp({ quality: 1 })
|
||||
.tile({
|
||||
layout: 'google'
|
||||
})
|
||||
.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);
|
||||
const sample = path.join(directory, '0', '0', '0.webp');
|
||||
sharp(sample).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', metadata.format);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasProfile);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
assert.strictEqual(true, metadata.width === 256);
|
||||
assert.strictEqual(true, metadata.height === 256);
|
||||
fs.stat(sample, function (err, stat) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.size < 2000);
|
||||
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');
|
||||
|
Loading…
x
Reference in New Issue
Block a user