diff --git a/lib/constructor.js b/lib/constructor.js index 05b4c2db3..f6741509b 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -325,6 +325,7 @@ const Sharp = function (input, options) { heifCompression: 'av1', heifEffort: 4, heifChromaSubsampling: '4:4:4', + heifBitdepth: 8, jxlDistance: 1, jxlDecodingTier: 0, jxlEffort: 7, diff --git a/lib/index.d.ts b/lib/index.d.ts index f73a7594b..c31ccdfc1 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1244,6 +1244,8 @@ declare namespace sharp { effort?: number | undefined; /** set to '4:2:0' to use chroma subsampling, requires libvips v8.11.0 (optional, default '4:4:4') */ chromaSubsampling?: string | undefined; + /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */ + bitdepth?: 8 | 10 | 12 | undefined; } interface HeifOptions extends OutputOptions { @@ -1257,6 +1259,8 @@ declare namespace sharp { effort?: number | undefined; /** set to '4:2:0' to use chroma subsampling (optional, default '4:4:4') */ chromaSubsampling?: string | undefined; + /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */ + bitdepth?: 8 | 10 | 12 | undefined; } interface GifOptions extends OutputOptions, AnimationOptions { diff --git a/lib/output.js b/lib/output.js index d192b7aff..c03d6506a 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1029,6 +1029,7 @@ function tiff (options) { * @param {boolean} [options.lossless=false] - use lossless compression * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest) * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling + * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit * @returns {Sharp} * @throws {Error} Invalid options */ @@ -1055,6 +1056,7 @@ function avif (options) { * @param {boolean} [options.lossless=false] - use lossless compression * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest) * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling + * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit * @returns {Sharp} * @throws {Error} Invalid options */ @@ -1093,6 +1095,13 @@ function heif (options) { throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling); } } + if (is.defined(options.bitdepth)) { + if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [8, 10, 12])) { + this.options.heifBitdepth = options.bitdepth; + } else { + throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth); + } + } } else { throw is.invalidParameterError('options', 'Object', options); } diff --git a/src/pipeline.cc b/src/pipeline.cc index ce5ce7ba9..a9c68f123 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -989,7 +989,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("Q", baton->heifQuality) ->set("compression", baton->heifCompression) ->set("effort", baton->heifEffort) - ->set("bitdepth", 8) + ->set("bitdepth", baton->heifBitdepth) ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("lossless", baton->heifLossless))); @@ -1182,7 +1182,7 @@ class PipelineWorker : public Napi::AsyncWorker { ->set("Q", baton->heifQuality) ->set("compression", baton->heifCompression) ->set("effort", baton->heifEffort) - ->set("bitdepth", 8) + ->set("bitdepth", baton->heifBitdepth) ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4" ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON) ->set("lossless", baton->heifLossless)); @@ -1696,6 +1696,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) { options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION); baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort"); baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling"); + baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth"); baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance"); baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier"); baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort"); diff --git a/src/pipeline.h b/src/pipeline.h index 3d92dbacf..6ebf0e63f 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -181,6 +181,7 @@ struct PipelineBaton { int heifEffort; std::string heifChromaSubsampling; bool heifLossless; + int heifBitdepth; double jxlDistance; int jxlDecodingTier; int jxlEffort; @@ -349,6 +350,7 @@ struct PipelineBaton { heifEffort(4), heifChromaSubsampling("4:4:4"), heifLossless(false), + heifBitdepth(8), jxlDistance(1.0), jxlDecodingTier(0), jxlEffort(7), diff --git a/test/unit/avif.js b/test/unit/avif.js index ca9a2bc23..3075d365d 100644 --- a/test/unit/avif.js +++ b/test/unit/avif.js @@ -144,4 +144,10 @@ describe('AVIF', () => { /Processed image is too large for the HEIF format/ ) ); + + it('Invalid bitdepth value throws error', async () => { + assert.rejects( + () => sharp().avif({ bitdepth: 11 }), + /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/); + }); }); diff --git a/test/unit/heif.js b/test/unit/heif.js index 338bda693..ccd6a3992 100644 --- a/test/unit/heif.js +++ b/test/unit/heif.js @@ -78,4 +78,14 @@ describe('HEIF', () => { sharp().heif({ compression: 'av1', chromaSubsampling: '4:4:4' }); }); }); + it('valid bitdepth value does not throw an error', () => { + assert.doesNotThrow(() => { + sharp().heif({ compression: 'av1', bitdepth: 12 }); + }); + }); + it('invalid bitdepth value should throw an error', () => { + assert.throws(() => { + sharp().heif({ compression: 'av1', bitdepth: 11 }); + }, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/); + }); });