Add support for OME-TIFF subIFDs #2557

This commit is contained in:
Lovell Fuller 2021-04-02 08:04:21 +01:00
parent 8c33d0aa56
commit 43a085d1ae
10 changed files with 52 additions and 4 deletions

View File

@ -28,6 +28,7 @@ Implements the [stream.Duplex][1] class.
- `options.density` **[number][8]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`) - `options.density` **[number][8]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
- `options.pages` **[number][8]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`) - `options.pages` **[number][8]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[number][8]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`) - `options.page` **[number][8]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.subifd` **[number][8]** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default `-1`)
- `options.level` **[number][8]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`) - `options.level` **[number][8]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
- `options.animated` **[boolean][7]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`) - `options.animated` **[boolean][7]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
- `options.raw` **[Object][6]?** describes raw pixel input image data. See `raw()` for pixel ordering. - `options.raw` **[Object][6]?** describes raw pixel input image data. See `raw()` for pixel ordering.

View File

@ -21,6 +21,7 @@ A `Promise` is returned when `callback` is not provided.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers. - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image - `pagePrimary`: Number of the primary page in a HEIF image
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
- `subifds`: Number of Sub Image File Directories in an OME-TIFF image
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present - `orientation`: Number value of the EXIF Orientation header, if present

View File

@ -8,6 +8,9 @@ Requires libvips v8.10.6
* Ensure all installation errors are logged with a more obvious prefix. * Ensure all installation errors are logged with a more obvious prefix.
* Add support for OME-TIFF Sub Image File Directories (subIFD).
[#2557](https://github.com/lovell/sharp/issues/2557)
* Allow `ensureAlpha` to set the alpha transparency level. * Allow `ensureAlpha` to set the alpha transparency level.
[#2634](https://github.com/lovell/sharp/issues/2634) [#2634](https://github.com/lovell/sharp/issues/2634)

View File

@ -132,6 +132,7 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000. * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based. * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.

View File

@ -9,9 +9,9 @@ const sharp = require('../build/Release/sharp.node');
* @private * @private
*/ */
function _inputOptionsFromObject (obj) { function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj; const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined) return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } ? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd }
: undefined; : undefined;
} }
@ -131,6 +131,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level); throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
} }
} }
// Sub Image File Directory (TIFF)
if (is.defined(inputOptions.subifd)) {
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
inputDescriptor.subifd = inputOptions.subifd;
} else {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
}
}
// Create new image // Create new image
if (is.defined(inputOptions.create)) { if (is.defined(inputOptions.create)) {
if ( if (
@ -255,6 +263,7 @@ function _isStreamInput () {
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers. * - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image * - `pagePrimary`: Number of the primary page in a HEIF image
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide * - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present * - `orientation`: Number value of the EXIF Orientation header, if present

View File

@ -104,6 +104,10 @@ namespace sharp {
if (HasAttr(input, "level")) { if (HasAttr(input, "level")) {
descriptor->level = AttrAsUint32(input, "level"); descriptor->level = AttrAsUint32(input, "level");
} }
// subIFD (OME-TIFF)
if (HasAttr(input, "subifd")) {
descriptor->subifd = AttrAsInt32(input, "subifd");
}
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrAsUint32(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
@ -319,6 +323,9 @@ namespace sharp {
if (imageType == ImageType::OPENSLIDE) { if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level); option->set("level", descriptor->level);
} }
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@ -391,6 +398,9 @@ namespace sharp {
if (imageType == ImageType::OPENSLIDE) { if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level); option->set("level", descriptor->level);
} }
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);

View File

@ -60,6 +60,7 @@ namespace sharp {
int pages; int pages;
int page; int page;
int level; int level;
int subifd;
int createChannels; int createChannels;
int createWidth; int createWidth;
int createHeight; int createHeight;
@ -82,6 +83,7 @@ namespace sharp {
pages(1), pages(1),
page(0), page(0),
level(0), level(0),
subifd(-1),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),

View File

@ -86,6 +86,9 @@ class MetadataWorker : public Napi::AsyncWorker {
baton->levels.push_back(std::pair<int, int>(width, height)); baton->levels.push_back(std::pair<int, int>(width, height));
} }
} }
if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
}
baton->hasProfile = sharp::HasProfile(image); baton->hasProfile = sharp::HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = sharp::HasAlpha(image); baton->hasAlpha = sharp::HasAlpha(image);
@ -203,6 +206,9 @@ class MetadataWorker : public Napi::AsyncWorker {
} }
info.Set("levels", levels); info.Set("levels", levels);
} }
if (baton->subifds > 0) {
info.Set("subifds", baton->subifds);
}
info.Set("hasProfile", baton->hasProfile); info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha); info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) { if (baton->orientation > 0) {

View File

@ -41,6 +41,7 @@ struct MetadataBaton {
int pagePrimary; int pagePrimary;
std::string compression; std::string compression;
std::vector<std::pair<int, int>> levels; std::vector<std::pair<int, int>> levels;
int subifds;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
@ -68,6 +69,7 @@ struct MetadataBaton {
pageHeight(0), pageHeight(0),
loop(-1), loop(-1),
pagePrimary(-1), pagePrimary(-1),
subifds(0),
hasProfile(false), hasProfile(false),
hasAlpha(false), hasAlpha(false),
orientation(0), orientation(0),

View File

@ -718,6 +718,19 @@ describe('Input/output', function () {
sharp({ level: -1 }); sharp({ level: -1 });
}, /Expected integer between 0 and 256 for level but received -1 of type number/); }, /Expected integer between 0 and 256 for level but received -1 of type number/);
}); });
it('Valid subifd property', function () {
sharp({ subifd: 1 });
});
it('Invalid subifd property (string) throws', function () {
assert.throws(function () {
sharp({ subifd: '1' });
}, /Expected integer between -1 and 100000 for subifd but received 1 of type string/);
});
it('Invalid subifd property (float) throws', function () {
assert.throws(function () {
sharp({ subifd: 1.2 });
}, /Expected integer between -1 and 100000 for subifd but received 1.2 of type number/);
});
}); });
describe('create new image', function () { describe('create new image', function () {