From 6b922b30d59e85d0ac04c7b899a8129e3675dcf9 Mon Sep 17 00:00:00 2001 From: throwbi Date: Tue, 30 Sep 2025 10:41:02 +0200 Subject: [PATCH] Add support for BigTIFF output (#4459) --- docs/src/content/docs/api-output.md | 1 + lib/constructor.js | 1 + lib/index.d.ts | 2 ++ lib/output.js | 5 +++++ src/pipeline.cc | 3 +++ src/pipeline.h | 2 ++ test/unit/tiff.js | 12 ++++++++++++ 7 files changed, 26 insertions(+) diff --git a/docs/src/content/docs/api-output.md b/docs/src/content/docs/api-output.md index 7c966fd7..800732a2 100644 --- a/docs/src/content/docs/api-output.md +++ b/docs/src/content/docs/api-output.md @@ -646,6 +646,7 @@ instead of providing `xres` and `yres` in pixels/mm. | [options.quality] | number | 80 | quality, integer 1-100 | | [options.force] | boolean | true | force TIFF output, otherwise attempt to use input format | | [options.compression] | string | "'jpeg'" | compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k | +| [options.bigtiff] | boolean | false | use BigTIFF variant (has no effect when compression is none) | | [options.predictor] | string | "'horizontal'" | compression predictor options: none, horizontal, float | | [options.pyramid] | boolean | false | write an image pyramid | | [options.tile] | boolean | false | write a tiled tiff | diff --git a/lib/constructor.js b/lib/constructor.js index 82acbd08..488b9f03 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -352,6 +352,7 @@ const Sharp = function (input, options) { gifProgressive: false, tiffQuality: 80, tiffCompression: 'jpeg', + tiffBigtiff: false, tiffPredictor: 'horizontal', tiffPyramid: false, tiffMiniswhite: false, diff --git a/lib/index.d.ts b/lib/index.d.ts index 83e010d8..ed99ecf8 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1460,6 +1460,8 @@ declare namespace sharp { quality?: number | undefined; /** Compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k (optional, default 'jpeg') */ compression?: string | undefined; + /** Use BigTIFF variant (has no effect when compression is none) (optional, default false) */ + bigtiff?: boolean | undefined; /** Compression predictor options: none, horizontal, float (optional, default 'horizontal') */ predictor?: string | undefined; /** Write an image pyramid (optional, default false) */ diff --git a/lib/output.js b/lib/output.js index 1178d625..8718c698 100644 --- a/lib/output.js +++ b/lib/output.js @@ -974,6 +974,7 @@ function trySetAnimationOptions (source, target) { * @param {number} [options.quality=80] - quality, integer 1-100 * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {string} [options.compression='jpeg'] - compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k + * @param {boolean} [options.bigtiff=false] - use BigTIFF variant (has no effect when compression is none) * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {boolean} [options.pyramid=false] - write an image pyramid * @param {boolean} [options.tile=false] - write a tiled tiff @@ -1052,6 +1053,10 @@ function tiff (options) { throw is.invalidParameterError('compression', 'one of: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k', options.compression); } } + // bigtiff + if (is.defined(options.bigtiff)) { + this._setBooleanOption('tiffBigtiff', options.bigtiff); + } // predictor if (is.defined(options.predictor)) { if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) { diff --git a/src/pipeline.cc b/src/pipeline.cc index 4a470b5a..2b952fc9 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -1009,6 +1009,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) + ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) @@ -1211,6 +1212,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("Q", baton->tiffQuality) ->set("bitdepth", baton->tiffBitdepth) ->set("compression", baton->tiffCompression) + ->set("bigtiff", baton->tiffBigtiff) ->set("miniswhite", baton->tiffMiniswhite) ->set("predictor", baton->tiffPredictor) ->set("pyramid", baton->tiffPyramid) @@ -1750,6 +1752,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); + baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth"); diff --git a/src/pipeline.h b/src/pipeline.h index 9fb89bdc..eaec71fa 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -175,6 +175,7 @@ struct PipelineBaton { bool gifProgressive; int tiffQuality; VipsForeignTiffCompression tiffCompression; + bool tiffBigtiff; VipsForeignTiffPredictor tiffPredictor; bool tiffPyramid; int tiffBitdepth; @@ -350,6 +351,7 @@ struct PipelineBaton { gifProgressive(false), tiffQuality(80), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), + tiffBigtiff(false), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPyramid(false), tiffBitdepth(8), diff --git a/test/unit/tiff.js b/test/unit/tiff.js index 8113d5a1..fb0b0e52 100644 --- a/test/unit/tiff.js +++ b/test/unit/tiff.js @@ -401,6 +401,18 @@ describe('TIFF', function () { }); }); + it('TIFF bigtiff true value does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ bigtiff: true }); + }); + }); + + it('Invalid TIFF bigtiff value throws error', function () { + assert.throws(function () { + sharp().tiff({ bigtiff: 'true' }); + }); + }); + it('TIFF invalid predictor option throws', function () { assert.throws(function () { sharp().tiff({ predictor: 'a' });