// Copyright 2013 Lovell Fuller and others. // SPDX-License-Identifier: Apache-2.0 'use strict'; const fs = require('fs'); const assert = require('assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); describe('PNG', function () { it('compression level is valid', function () { assert.doesNotThrow(function () { sharp().png({ compressionLevel: 0 }); }); }); it('compression level is invalid', function () { assert.throws(function () { sharp().png({ compressionLevel: -1 }); }); }); it('default compressionLevel generates smaller file than compressionLevel=0', function (done) { // First generate with default compressionLevel sharp(fixtures.inputPng) .resize(320, 240) .png() .toBuffer(function (err, defaultData, defaultInfo) { if (err) throw err; assert.strictEqual(true, defaultData.length > 0); assert.strictEqual('png', defaultInfo.format); // Then generate with compressionLevel=6 sharp(fixtures.inputPng) .resize(320, 240) .png({ compressionLevel: 0 }) .toBuffer(function (err, largerData, largerInfo) { if (err) throw err; assert.strictEqual(true, largerData.length > 0); assert.strictEqual('png', largerInfo.format); assert.strictEqual(true, defaultData.length < largerData.length); done(); }); }); }); it('without adaptiveFiltering generates smaller file', function (done) { // First generate with adaptive filtering sharp(fixtures.inputPng) .resize(320, 240) .png({ adaptiveFiltering: true }) .toBuffer(function (err, adaptiveData, adaptiveInfo) { if (err) throw err; assert.strictEqual(true, adaptiveData.length > 0); assert.strictEqual(adaptiveData.length, adaptiveInfo.size); assert.strictEqual('png', adaptiveInfo.format); assert.strictEqual(320, adaptiveInfo.width); assert.strictEqual(240, adaptiveInfo.height); // Then generate without sharp(fixtures.inputPng) .resize(320, 240) .png({ adaptiveFiltering: false }) .toBuffer(function (err, withoutAdaptiveData, withoutAdaptiveInfo) { if (err) throw err; assert.strictEqual(true, withoutAdaptiveData.length > 0); assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size); assert.strictEqual('png', withoutAdaptiveInfo.format); assert.strictEqual(320, withoutAdaptiveInfo.width); assert.strictEqual(240, withoutAdaptiveInfo.height); assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length); done(); }); }); }); it('Invalid PNG adaptiveFiltering value throws error', function () { assert.throws(function () { sharp().png({ adaptiveFiltering: 1 }); }); }); it('Progressive PNG image', function (done) { sharp(fixtures.inputJpg) .resize(320, 240) .png({ progressive: false }) .toBuffer(function (err, nonProgressiveData, nonProgressiveInfo) { if (err) throw err; assert.strictEqual(true, nonProgressiveData.length > 0); assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size); assert.strictEqual('png', nonProgressiveInfo.format); assert.strictEqual(320, nonProgressiveInfo.width); assert.strictEqual(240, nonProgressiveInfo.height); sharp(nonProgressiveData) .png({ progressive: true }) .toBuffer(function (err, progressiveData, progressiveInfo) { if (err) throw err; assert.strictEqual(true, progressiveData.length > 0); assert.strictEqual(progressiveData.length, progressiveInfo.size); assert.strictEqual(true, progressiveData.length > nonProgressiveData.length); assert.strictEqual('png', progressiveInfo.format); assert.strictEqual(320, progressiveInfo.width); assert.strictEqual(240, progressiveInfo.height); done(); }); }); }); it('16-bit grey+alpha PNG identity transform', function () { const actual = fixtures.path('output.16-bit-grey-alpha-identity.png'); return sharp(fixtures.inputPng16BitGreyAlpha) .toFile(actual) .then(function () { fixtures.assertMaxColourDistance(actual, fixtures.expected('16-bit-grey-alpha-identity.png')); }); }); it('16-bit grey+alpha PNG roundtrip', async () => { const after = await sharp(fixtures.inputPng16BitGreyAlpha) .toColourspace('grey16') .toBuffer(); const [alphaMeanBefore, alphaMeanAfter] = ( await Promise.all([ sharp(fixtures.inputPng16BitGreyAlpha).stats(), sharp(after).stats() ]) ) .map(stats => stats.channels[1].mean); assert.strictEqual(alphaMeanAfter, alphaMeanBefore); }); it('palette decode/encode roundtrip', async () => { const data = await sharp(fixtures.inputPngPalette) .png({ effort: 1, palette: true }) .toBuffer(); const { size, ...metadata } = await sharp(data).metadata(); assert.deepStrictEqual(metadata, { autoOrient: { height: 68, width: 68 }, format: 'png', width: 68, height: 68, space: 'srgb', channels: 3, density: 72, depth: 'uchar', isProgressive: false, isPalette: true, bitsPerSample: 8, paletteBitDepth: 8, hasProfile: false, hasAlpha: false }); }); it('Valid PNG libimagequant palette value does not throw error', function () { assert.doesNotThrow(function () { sharp().png({ palette: false }); }); }); it('Invalid PNG libimagequant palette value throws error', function () { assert.throws(function () { sharp().png({ palette: 'fail' }); }); }); it('Valid PNG libimagequant quality value produces image of same size or smaller', function () { const inputPngBuffer = fs.readFileSync(fixtures.inputPng); return Promise.all([ sharp(inputPngBuffer).resize(10).png({ effort: 1, quality: 80 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ effort: 1, quality: 100 }).toBuffer() ]).then(function (data) { assert.strictEqual(true, data[0].length <= data[1].length); }); }); it('Invalid PNG libimagequant quality value throws error', function () { assert.throws(function () { sharp().png({ quality: 101 }); }); }); it('Invalid effort value throws error', () => { assert.throws(() => { sharp().png({ effort: 0.1 }); }); }); it('Valid PNG libimagequant colours value produces image of same size or smaller', function () { const inputPngBuffer = fs.readFileSync(fixtures.inputPng); return Promise.all([ sharp(inputPngBuffer).resize(10).png({ colours: 100 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ colours: 200 }).toBuffer() ]).then(function (data) { assert.strictEqual(true, data[0].length <= data[1].length); }); }); it('Invalid PNG libimagequant colours value throws error', function () { assert.throws(function () { sharp().png({ colours: -1 }); }); }); it('Invalid PNG libimagequant colors value throws error', function () { assert.throws(function () { sharp().png({ colors: 0.1 }); }); }); it('Can set bitdepth of PNG without palette', async () => { const data = await sharp({ create: { width: 8, height: 8, channels: 3, background: 'red' } }) .toColourspace('b-w') .png({ colours: 2, palette: false }) .toBuffer(); const { channels, isPalette, bitsPerSample, paletteBitDepth, size, space } = await sharp(data).metadata(); assert.strictEqual(channels, 1); assert.strictEqual(isPalette, false); assert.strictEqual(bitsPerSample, 1); assert.strictEqual(paletteBitDepth, undefined); assert.strictEqual(size, 89); assert.strictEqual(space, 'b-w'); }); it('Valid PNG libimagequant dither value produces image of same size or smaller', function () { const inputPngBuffer = fs.readFileSync(fixtures.inputPng); return Promise.all([ sharp(inputPngBuffer).resize(10).png({ dither: 0.1 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ dither: 0.9 }).toBuffer() ]).then(function (data) { assert.strictEqual(true, data[0].length <= data[1].length); }); }); it('Invalid PNG libimagequant dither value throws error', function () { assert.throws(function () { sharp().png({ dither: 'fail' }); }); }); });