mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add support for libvips compiled with OpenJPEG
This commit is contained in:
parent
5d98bcd8d8
commit
b7fbffb3f7
@ -330,6 +330,47 @@ The prebuilt binaries do not include this - see
|
|||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
|
## jp2
|
||||||
|
|
||||||
|
Use these JP2 options for output image.
|
||||||
|
|
||||||
|
Requires libvips compiled with support for OpenJPEG.
|
||||||
|
The prebuilt binaries do not include this - see
|
||||||
|
[installing a custom libvips][11].
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
* `options` **[Object][6]?** output options
|
||||||
|
|
||||||
|
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||||
|
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
|
||||||
|
* `options.tileWidth` **[number][9]** horizontal tile size (optional, default `512`)
|
||||||
|
* `options.tileHeight` **[number][9]** vertical tile size (optional, default `512`)
|
||||||
|
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Convert any input to lossless JP2 output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jp2({ lossless: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Convert any input to very high quality JP2 output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jp2({
|
||||||
|
quality: 100,
|
||||||
|
chromaSubsampling: '4:4:4'
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
* Throws **[Error][4]** Invalid options
|
||||||
|
|
||||||
|
Returns **Sharp**
|
||||||
|
|
||||||
## tiff
|
## tiff
|
||||||
|
|
||||||
Use these TIFF options for output image.
|
Use these TIFF options for output image.
|
||||||
|
@ -235,6 +235,11 @@ const Sharp = function (input, options) {
|
|||||||
pngQuality: 100,
|
pngQuality: 100,
|
||||||
pngBitdepth: 8,
|
pngBitdepth: 8,
|
||||||
pngDither: 1,
|
pngDither: 1,
|
||||||
|
jp2Quality: 80,
|
||||||
|
jp2TileHeight: 512,
|
||||||
|
jp2TileWidth: 512,
|
||||||
|
jp2Lossless: false,
|
||||||
|
jp2ChromaSubsampling: '4:4:4',
|
||||||
webpQuality: 80,
|
webpQuality: 80,
|
||||||
webpAlphaQuality: 100,
|
webpAlphaQuality: 100,
|
||||||
webpLossless: false,
|
webpLossless: false,
|
||||||
|
@ -13,10 +13,15 @@ const formats = new Map([
|
|||||||
['raw', 'raw'],
|
['raw', 'raw'],
|
||||||
['tiff', 'tiff'],
|
['tiff', 'tiff'],
|
||||||
['webp', 'webp'],
|
['webp', 'webp'],
|
||||||
['gif', 'gif']
|
['gif', 'gif'],
|
||||||
|
['jp2', 'jp2'],
|
||||||
|
['jpx', 'jp2'],
|
||||||
|
['j2k', 'jp2'],
|
||||||
|
['j2c', 'jp2']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
|
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
|
||||||
|
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write output image data to a file.
|
* Write output image data to a file.
|
||||||
@ -511,6 +516,82 @@ function gif (options) {
|
|||||||
return this._updateFormatOut('gif', options);
|
return this._updateFormatOut('gif', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use these JP2 options for output image.
|
||||||
|
*
|
||||||
|
* Requires libvips compiled with support for OpenJPEG.
|
||||||
|
* The prebuilt binaries do not include this - see
|
||||||
|
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Convert any input to lossless JP2 output
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .jp2({ lossless: true })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Convert any input to very high quality JP2 output
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .jp2({
|
||||||
|
* quality: 100,
|
||||||
|
* chromaSubsampling: '4:4:4'
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @param {Object} [options] - output options
|
||||||
|
* @param {number} [options.quality=80] - quality, integer 1-100
|
||||||
|
* @param {boolean} [options.lossless=false] - use lossless compression mode
|
||||||
|
* @param {number} [options.tileWidth=512] - horizontal tile size
|
||||||
|
* @param {number} [options.tileHeight=512] - vertical tile size
|
||||||
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid options
|
||||||
|
*/
|
||||||
|
/* istanbul ignore next */
|
||||||
|
function jp2 (options) {
|
||||||
|
if (!this.constructor.format.jp2k.output.buffer) {
|
||||||
|
throw errJp2Save;
|
||||||
|
}
|
||||||
|
if (is.object(options)) {
|
||||||
|
if (is.defined(options.quality)) {
|
||||||
|
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||||
|
this.options.jp2Quality = options.quality;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.lossless)) {
|
||||||
|
if (is.bool(options.lossless)) {
|
||||||
|
this.options.jp2Lossless = options.lossless;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.tileWidth)) {
|
||||||
|
if (is.integer(options.tileWidth) && is.inRange(options.tileWidth, 1, 32768)) {
|
||||||
|
this.options.jp2TileWidth = options.tileWidth;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tileWidth', 'integer between 1 and 32768', options.tileWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.tileHeight)) {
|
||||||
|
if (is.integer(options.tileHeight) && is.inRange(options.tileHeight, 1, 32768)) {
|
||||||
|
this.options.jp2TileHeight = options.tileHeight;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tileHeight', 'integer between 1 and 32768', options.tileHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.chromaSubsampling)) {
|
||||||
|
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
||||||
|
this.options.heifChromaSubsampling = options.chromaSubsampling;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._updateFormatOut('jp2', options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set animation options if available.
|
* Set animation options if available.
|
||||||
* @private
|
* @private
|
||||||
@ -1035,6 +1116,7 @@ module.exports = function (Sharp) {
|
|||||||
withMetadata,
|
withMetadata,
|
||||||
toFormat,
|
toFormat,
|
||||||
jpeg,
|
jpeg,
|
||||||
|
jp2,
|
||||||
png,
|
png,
|
||||||
webp,
|
webp,
|
||||||
tiff,
|
tiff,
|
||||||
|
@ -78,7 +78,8 @@
|
|||||||
"Jacob Smith <jacob@frende.me>",
|
"Jacob Smith <jacob@frende.me>",
|
||||||
"Michael Nutt <michael@nutt.im>",
|
"Michael Nutt <michael@nutt.im>",
|
||||||
"Brad Parham <baparham@gmail.com>",
|
"Brad Parham <baparham@gmail.com>",
|
||||||
"Taneli Vatanen <taneli.vatanen@gmail.com>"
|
"Taneli Vatanen <taneli.vatanen@gmail.com>",
|
||||||
|
"Joris Dugué <zaruike10@gmail.com>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"tiff",
|
"tiff",
|
||||||
"gif",
|
"gif",
|
||||||
"svg",
|
"svg",
|
||||||
|
"jp2",
|
||||||
"dzi",
|
"dzi",
|
||||||
"image",
|
"image",
|
||||||
"resize",
|
"resize",
|
||||||
|
@ -157,6 +157,10 @@ namespace sharp {
|
|||||||
bool IsGif(std::string const &str) {
|
bool IsGif(std::string const &str) {
|
||||||
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
|
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
|
||||||
}
|
}
|
||||||
|
bool IsJp2(std::string const &str) {
|
||||||
|
return EndsWith(str, ".jp2") || EndsWith(str, ".jpx") || EndsWith(str, ".j2k") || EndsWith(str, ".j2c")
|
||||||
|
|| EndsWith(str, ".JP2") || EndsWith(str, ".JPX") || EndsWith(str, ".J2K") || EndsWith(str, ".J2C");
|
||||||
|
}
|
||||||
bool IsTiff(std::string const &str) {
|
bool IsTiff(std::string const &str) {
|
||||||
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
||||||
}
|
}
|
||||||
@ -190,6 +194,7 @@ namespace sharp {
|
|||||||
case ImageType::WEBP: id = "webp"; break;
|
case ImageType::WEBP: id = "webp"; break;
|
||||||
case ImageType::TIFF: id = "tiff"; break;
|
case ImageType::TIFF: id = "tiff"; break;
|
||||||
case ImageType::GIF: id = "gif"; break;
|
case ImageType::GIF: id = "gif"; break;
|
||||||
|
case ImageType::JP2: id = "jp2"; break;
|
||||||
case ImageType::SVG: id = "svg"; break;
|
case ImageType::SVG: id = "svg"; break;
|
||||||
case ImageType::HEIF: id = "heif"; break;
|
case ImageType::HEIF: id = "heif"; break;
|
||||||
case ImageType::PDF: id = "pdf"; break;
|
case ImageType::PDF: id = "pdf"; break;
|
||||||
@ -226,6 +231,8 @@ namespace sharp {
|
|||||||
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
||||||
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
||||||
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
||||||
|
{ "VipsForeignLoadJp2kBuffer", ImageType::JP2 },
|
||||||
|
{ "VipsForeignLoadJp2kFile", ImageType::JP2 },
|
||||||
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
||||||
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
||||||
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
||||||
@ -287,6 +294,7 @@ namespace sharp {
|
|||||||
imageType == ImageType::WEBP ||
|
imageType == ImageType::WEBP ||
|
||||||
imageType == ImageType::MAGICK ||
|
imageType == ImageType::MAGICK ||
|
||||||
imageType == ImageType::GIF ||
|
imageType == ImageType::GIF ||
|
||||||
|
imageType == ImageType::JP2 ||
|
||||||
imageType == ImageType::TIFF ||
|
imageType == ImageType::TIFF ||
|
||||||
imageType == ImageType::HEIF ||
|
imageType == ImageType::HEIF ||
|
||||||
imageType == ImageType::PDF;
|
imageType == ImageType::PDF;
|
||||||
|
@ -116,6 +116,7 @@ namespace sharp {
|
|||||||
JPEG,
|
JPEG,
|
||||||
PNG,
|
PNG,
|
||||||
WEBP,
|
WEBP,
|
||||||
|
JP2,
|
||||||
TIFF,
|
TIFF,
|
||||||
GIF,
|
GIF,
|
||||||
SVG,
|
SVG,
|
||||||
@ -142,6 +143,7 @@ namespace sharp {
|
|||||||
bool IsJpeg(std::string const &str);
|
bool IsJpeg(std::string const &str);
|
||||||
bool IsPng(std::string const &str);
|
bool IsPng(std::string const &str);
|
||||||
bool IsWebp(std::string const &str);
|
bool IsWebp(std::string const &str);
|
||||||
|
bool IsJp2(std::string const &str);
|
||||||
bool IsGif(std::string const &str);
|
bool IsGif(std::string const &str);
|
||||||
bool IsTiff(std::string const &str);
|
bool IsTiff(std::string const &str);
|
||||||
bool IsHeic(std::string const &str);
|
bool IsHeic(std::string const &str);
|
||||||
|
@ -791,6 +791,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
} else {
|
} else {
|
||||||
baton->channels = std::min(baton->channels, 3);
|
baton->channels = std::min(baton->channels, 3);
|
||||||
}
|
}
|
||||||
|
} else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
|
||||||
|
&& inputImageType == sharp::ImageType::JP2)) {
|
||||||
|
// Write JP2 to Buffer
|
||||||
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
||||||
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option()
|
||||||
|
->set("Q", baton->jp2Quality)
|
||||||
|
->set("lossless", baton->jp2Lossless)
|
||||||
|
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
||||||
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
|
->set("tile_height", baton->jp2TileHeight)
|
||||||
|
->set("tile_width", baton->jp2TileWidth)));
|
||||||
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
|
baton->bufferOutLength = area->length;
|
||||||
|
area->free_fn = nullptr;
|
||||||
|
vips_area_unref(area);
|
||||||
|
baton->formatOut = "jp2";
|
||||||
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
||||||
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
||||||
inputImageType == sharp::ImageType::SVG))) {
|
inputImageType == sharp::ImageType::SVG))) {
|
||||||
@ -922,13 +938,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
||||||
bool const isGif = sharp::IsGif(baton->fileOut);
|
bool const isGif = sharp::IsGif(baton->fileOut);
|
||||||
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
||||||
|
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
||||||
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
||||||
bool const isDz = sharp::IsDz(baton->fileOut);
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
||||||
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
||||||
bool const isV = sharp::IsV(baton->fileOut);
|
bool const isV = sharp::IsV(baton->fileOut);
|
||||||
bool const mightMatchInput = baton->formatOut == "input";
|
bool const mightMatchInput = baton->formatOut == "input";
|
||||||
bool const willMatchInput = mightMatchInput &&
|
bool const willMatchInput = mightMatchInput &&
|
||||||
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
!(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
|
||||||
|
|
||||||
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
||||||
@ -948,6 +965,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("optimize_coding", baton->jpegOptimiseCoding));
|
->set("optimize_coding", baton->jpegOptimiseCoding));
|
||||||
baton->formatOut = "jpeg";
|
baton->formatOut = "jpeg";
|
||||||
baton->channels = std::min(baton->channels, 3);
|
baton->channels = std::min(baton->channels, 3);
|
||||||
|
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
||||||
|
(willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
|
||||||
|
// Write JP2 to file
|
||||||
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
||||||
|
image.jp2ksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
|
->set("Q", baton->jp2Quality)
|
||||||
|
->set("lossless", baton->jp2Lossless)
|
||||||
|
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
||||||
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
|
->set("tile_height", baton->jp2TileHeight)
|
||||||
|
->set("tile_width", baton->jp2TileWidth));
|
||||||
|
baton->formatOut = "jp2";
|
||||||
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
||||||
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
||||||
inputImageType == sharp::ImageType::SVG))) {
|
inputImageType == sharp::ImageType::SVG))) {
|
||||||
@ -1438,6 +1467,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
||||||
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
||||||
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
||||||
|
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
|
||||||
|
baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless");
|
||||||
|
baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight");
|
||||||
|
baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth");
|
||||||
|
baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling");
|
||||||
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
||||||
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
||||||
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
||||||
|
@ -149,6 +149,11 @@ struct PipelineBaton {
|
|||||||
int pngQuality;
|
int pngQuality;
|
||||||
int pngBitdepth;
|
int pngBitdepth;
|
||||||
double pngDither;
|
double pngDither;
|
||||||
|
int jp2Quality;
|
||||||
|
bool jp2Lossless;
|
||||||
|
int jp2TileHeight;
|
||||||
|
int jp2TileWidth;
|
||||||
|
std::string jp2ChromaSubsampling;
|
||||||
int webpQuality;
|
int webpQuality;
|
||||||
int webpAlphaQuality;
|
int webpAlphaQuality;
|
||||||
bool webpNearLossless;
|
bool webpNearLossless;
|
||||||
@ -280,6 +285,11 @@ struct PipelineBaton {
|
|||||||
pngQuality(100),
|
pngQuality(100),
|
||||||
pngBitdepth(8),
|
pngBitdepth(8),
|
||||||
pngDither(1.0),
|
pngDither(1.0),
|
||||||
|
jp2Quality(80),
|
||||||
|
jp2Lossless(false),
|
||||||
|
jp2TileHeight(512),
|
||||||
|
jp2TileWidth(512),
|
||||||
|
jp2ChromaSubsampling("4:4:4"),
|
||||||
webpQuality(80),
|
webpQuality(80),
|
||||||
webpAlphaQuality(100),
|
webpAlphaQuality(100),
|
||||||
webpNearLossless(false),
|
webpNearLossless(false),
|
||||||
|
@ -115,7 +115,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
|
|||||||
Napi::Object format = Napi::Object::New(env);
|
Napi::Object format = Napi::Object::New(env);
|
||||||
for (std::string const f : {
|
for (std::string const f : {
|
||||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
||||||
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
|
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k"
|
||||||
}) {
|
}) {
|
||||||
// Input
|
// Input
|
||||||
Napi::Boolean hasInputFile =
|
Napi::Boolean hasInputFile =
|
||||||
|
2
test/fixtures/index.js
vendored
2
test/fixtures/index.js
vendored
@ -105,6 +105,8 @@ module.exports = {
|
|||||||
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
|
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
|
||||||
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
|
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
|
||||||
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
|
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
|
||||||
|
|
||||||
|
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
|
||||||
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
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
|
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
|
||||||
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
|
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
|
||||||
|
BIN
test/fixtures/relax.jp2
vendored
Normal file
BIN
test/fixtures/relax.jp2
vendored
Normal file
Binary file not shown.
99
test/unit/jp2.js
Normal file
99
test/unit/jp2.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('JP2 output', () => {
|
||||||
|
if (!sharp.format.jp2k.input.buffer) {
|
||||||
|
it('JP2 output should fail due to missing OpenJPEG', () => {
|
||||||
|
assert.rejects(() =>
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.jp2()
|
||||||
|
.toBuffer(),
|
||||||
|
/JP2 output requires libvips with support for OpenJPEG/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JP2 file output should fail due to missing OpenJPEG', () => {
|
||||||
|
assert.rejects(async () => await sharp().toFile('test.jp2'),
|
||||||
|
/JP2 output requires libvips with support for OpenJPEG/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
it('JP2 Buffer to PNG Buffer', () => {
|
||||||
|
sharp(fs.readFileSync(fixtures.inputJp2))
|
||||||
|
.resize(8, 15)
|
||||||
|
.png()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(8, info.width);
|
||||||
|
assert.strictEqual(15, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JP2 quality', function (done) {
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ quality: 70 })
|
||||||
|
.toBuffer(function (err, buffer70) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toBuffer(function (err, buffer80) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ quality: 90 })
|
||||||
|
.toBuffer(function (err, buffer90) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert(buffer70.length < buffer80.length);
|
||||||
|
assert(buffer80.length < buffer90.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Without chroma subsampling generates larger file', function (done) {
|
||||||
|
// First generate with chroma subsampling (default)
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ chromaSubsampling: '4:2:0' })
|
||||||
|
.toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withChromaSubsamplingData.length > 0);
|
||||||
|
assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
|
||||||
|
assert.strictEqual('jp2', withChromaSubsamplingInfo.format);
|
||||||
|
assert.strictEqual(320, withChromaSubsamplingInfo.width);
|
||||||
|
assert.strictEqual(240, withChromaSubsamplingInfo.height);
|
||||||
|
// Then generate without
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ chromaSubsampling: '4:4:4' })
|
||||||
|
.toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
|
||||||
|
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
|
||||||
|
assert.strictEqual('jp2', withoutChromaSubsamplingInfo.format);
|
||||||
|
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
|
||||||
|
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
|
||||||
|
assert.strictEqual(true, withChromaSubsamplingData.length <= withoutChromaSubsamplingData.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid JP2 chromaSubsampling value throws error', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().jpeg({ chromaSubsampling: '4:2:2' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user