diff --git a/lib/output.mjs b/lib/output.mjs index aa93d067d..00ff8b9e1 100644 --- a/lib/output.mjs +++ b/lib/output.mjs @@ -33,7 +33,7 @@ const jp2Regex = /\.(jp[2x]|j2[kc])$/i; const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG'); -const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours))); +const bitdepthFromColourCount = (colours) => 1 << 32 - Math.clz32(Math.ceil(Math.log2(colours)) - 1); /** * Write output image data to a file. diff --git a/test/unit/gif.js b/test/unit/gif.js index 17e5f8c65..5e2d49651 100644 --- a/test/unit/gif.js +++ b/test/unit/gif.js @@ -74,7 +74,7 @@ suite('GIF input', () => { const reduced = await sharp(fixtures.inputJpg) .resize(120, 80) .gif({ - colours: 128, + colours: 16, dither: 0, effort: 1 }) diff --git a/test/unit/png.js b/test/unit/png.js index 655133791..21202165d 100644 --- a/test/unit/png.js +++ b/test/unit/png.js @@ -201,6 +201,31 @@ suite('PNG', () => { t.assert.strictEqual(true, data[0].length <= data[1].length); }); + test('PNG palette bit depth is large enough to hold the requested number of colours', async (t) => { + const colourCounts = [8, 17, 100, 128]; + t.plan(colourCounts.length); + // 256-colour gradient so quantisation genuinely fills the palette + const width = 256; + const height = 16; + const channels = 3; + const gradient = Buffer.alloc(width * height * channels); + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + const i = (y * width + x) * channels; + gradient[i] = x; + gradient[i + 1] = (x * 2) % 256; + gradient[i + 2] = 255 - x; + } + } + for (const colours of colourCounts) { + const data = await sharp(gradient, { raw: { width, height, channels } }) + .png({ colours, palette: true }) + .toBuffer(); + const { bitsPerSample } = await sharp(data).metadata(); + t.assert.ok(2 ** bitsPerSample >= colours, `colours ${colours} needs a palette of at least that size, got bit depth ${bitsPerSample}`); + } + }); + test('Invalid PNG libimagequant colours value throws error', (t) => { t.plan(1); t.assert.throws(() => {