diff --git a/test/unit/io.js b/test/unit/io.js index f5507dd6..130ab0ea 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -349,162 +349,6 @@ describe('Input/output', function () { .toBuffer(); }); - it('JPEG quality', function (done) { - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ quality: 70 }) - .toBuffer(function (err, buffer70) { - if (err) throw err; - sharp(fixtures.inputJpg) - .resize(320, 240) - .toBuffer(function (err, buffer80) { - if (err) throw err; - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ quality: 90 }) - .toBuffer(function (err, buffer90) { - if (err) throw err; - assert(buffer70.length < buffer80.length); - assert(buffer80.length < buffer90.length); - done(); - }); - }); - }); - }); - - describe('Invalid JPEG quality', function () { - [-1, 88.2, 'test'].forEach(function (quality) { - it(quality.toString(), function () { - assert.throws(function () { - sharp().jpeg({ quality: quality }); - }); - }); - }); - }); - - describe('Invalid JPEG quantisation table', function () { - [-1, 88.2, 'test'].forEach(function (table) { - it(table.toString(), function () { - assert.throws(function () { - sharp().jpeg({ quantisationTable: table }); - }); - }); - }); - }); - - it('Progressive JPEG image', function (done) { - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ 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('jpeg', nonProgressiveInfo.format); - assert.strictEqual(320, nonProgressiveInfo.width); - assert.strictEqual(240, nonProgressiveInfo.height); - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ 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(false, progressiveData.length === nonProgressiveData.length); - assert.strictEqual('jpeg', progressiveInfo.format); - assert.strictEqual(320, progressiveInfo.width); - assert.strictEqual(240, progressiveInfo.height); - done(); - }); - }); - }); - - 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(); - }); - }); - }); - - if (sharp.format.webp.output.buffer) { - it('WebP output', function (done) { - sharp(fixtures.inputJpg) - .resize(320, 240) - .toFormat(sharp.format.webp) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('webp', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - done(); - }); - }); - - it('should work for webp alpha quality', function (done) { - sharp(fixtures.inputPngAlphaPremultiplicationSmall) - .webp({ alphaQuality: 80 }) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('webp', info.format); - fixtures.assertSimilar(fixtures.expected('webp-alpha-80.webp'), data, done); - }); - }); - - it('should work for webp lossless', function (done) { - sharp(fixtures.inputPngAlphaPremultiplicationSmall) - .webp({ lossless: true }) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('webp', info.format); - fixtures.assertSimilar(fixtures.expected('webp-lossless.webp'), data, done); - }); - }); - - it('should work for webp near-lossless', function (done) { - sharp(fixtures.inputPngAlphaPremultiplicationSmall) - .webp({ nearLossless: true, quality: 50 }) - .toBuffer(function (err50, data50, info50) { - if (err50) throw err50; - assert.strictEqual(true, data50.length > 0); - assert.strictEqual('webp', info50.format); - fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done); - }); - }); - - it('should use near-lossless when both lossless and nearLossless are specified', function (done) { - sharp(fixtures.inputPngAlphaPremultiplicationSmall) - .webp({ nearLossless: true, quality: 50, lossless: true }) - .toBuffer(function (err50, data50, info50) { - if (err50) throw err50; - assert.strictEqual(true, data50.length > 0); - assert.strictEqual('webp', info50.format); - fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done); - }); - }); - } - it('Invalid output format', function (done) { let isValid = false; try { @@ -531,22 +375,6 @@ describe('Input/output', function () { }); }); - it('TIFF file input with invalid page fails gracefully', function (done) { - sharp(fixtures.inputTiffMultipage, { page: 2 }) - .toBuffer(function (err) { - assert.strictEqual(true, !!err); - done(); - }); - }); - - it('TIFF buffer input with invalid page fails gracefully', function (done) { - sharp(fs.readFileSync(fixtures.inputTiffMultipage), { page: 2 }) - .toBuffer(function (err) { - assert.strictEqual(true, !!err); - done(); - }); - }); - describe('Output filename with unknown extension', function () { it('Match JPEG input', function (done) { sharp(fixtures.inputJpg) @@ -628,669 +456,6 @@ describe('Input/output', function () { }); }); - it('Without chroma subsampling generates larger file', function (done) { - // First generate with chroma subsampling (default) - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ chromaSubsampling: '4:2:0' }) - .toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) { - if (err) throw err; - assert.strictEqual(true, withChromaSubsamplingData.length > 0); - assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size); - assert.strictEqual('jpeg', withChromaSubsamplingInfo.format); - assert.strictEqual(320, withChromaSubsamplingInfo.width); - assert.strictEqual(240, withChromaSubsamplingInfo.height); - // Then generate without - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ chromaSubsampling: '4:4:4' }) - .toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) { - if (err) throw err; - assert.strictEqual(true, withoutChromaSubsamplingData.length > 0); - assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size); - assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format); - assert.strictEqual(320, withoutChromaSubsamplingInfo.width); - assert.strictEqual(240, withoutChromaSubsamplingInfo.height); - assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length); - done(); - }); - }); - }); - - it('Invalid JPEG chromaSubsampling value throws error', function () { - assert.throws(function () { - sharp().jpeg({ chromaSubsampling: '4:2:2' }); - }); - }); - - it('Trellis quantisation', function (done) { - // First generate without - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ trellisQuantisation: false }) - .toBuffer(function (err, withoutData, withoutInfo) { - if (err) throw err; - assert.strictEqual(true, withoutData.length > 0); - assert.strictEqual(withoutData.length, withoutInfo.size); - assert.strictEqual('jpeg', withoutInfo.format); - assert.strictEqual(320, withoutInfo.width); - assert.strictEqual(240, withoutInfo.height); - // Then generate with - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ trellisQuantization: true }) - .toBuffer(function (err, withData, withInfo) { - if (err) throw err; - assert.strictEqual(true, withData.length > 0); - assert.strictEqual(withData.length, withInfo.size); - assert.strictEqual('jpeg', withInfo.format); - assert.strictEqual(320, withInfo.width); - assert.strictEqual(240, withInfo.height); - // Verify image is same (as mozjpeg may not be present) size or less - assert.strictEqual(true, withData.length <= withoutData.length); - done(); - }); - }); - }); - - it('Overshoot deringing', function (done) { - // First generate without - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ overshootDeringing: false }) - .toBuffer(function (err, withoutData, withoutInfo) { - if (err) throw err; - assert.strictEqual(true, withoutData.length > 0); - assert.strictEqual(withoutData.length, withoutInfo.size); - assert.strictEqual('jpeg', withoutInfo.format); - assert.strictEqual(320, withoutInfo.width); - assert.strictEqual(240, withoutInfo.height); - // Then generate with - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ overshootDeringing: true }) - .toBuffer(function (err, withData, withInfo) { - if (err) throw err; - assert.strictEqual(true, withData.length > 0); - assert.strictEqual(withData.length, withInfo.size); - assert.strictEqual('jpeg', withInfo.format); - assert.strictEqual(320, withInfo.width); - assert.strictEqual(240, withInfo.height); - done(); - }); - }); - }); - - it('Optimise scans generates different output length', function (done) { - // First generate without - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ optimiseScans: false }) - .toBuffer(function (err, withoutData, withoutInfo) { - if (err) throw err; - assert.strictEqual(true, withoutData.length > 0); - assert.strictEqual(withoutData.length, withoutInfo.size); - assert.strictEqual('jpeg', withoutInfo.format); - assert.strictEqual(320, withoutInfo.width); - assert.strictEqual(240, withoutInfo.height); - // Then generate with - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ optimizeScans: true }) - .toBuffer(function (err, withData, withInfo) { - if (err) throw err; - assert.strictEqual(true, withData.length > 0); - assert.strictEqual(withData.length, withInfo.size); - assert.strictEqual('jpeg', withInfo.format); - assert.strictEqual(320, withInfo.width); - assert.strictEqual(240, withInfo.height); - // Verify image is of a different size (progressive output even without mozjpeg) - assert.notStrictEqual(withData.length, withoutData.length); - done(); - }); - }); - }); - - it('Optimise coding generates smaller output length', function (done) { - // First generate with optimize coding enabled (default) - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg() - .toBuffer(function (err, withOptimiseCoding, withInfo) { - if (err) throw err; - assert.strictEqual(true, withOptimiseCoding.length > 0); - assert.strictEqual(withOptimiseCoding.length, withInfo.size); - assert.strictEqual('jpeg', withInfo.format); - assert.strictEqual(320, withInfo.width); - assert.strictEqual(240, withInfo.height); - // Then generate with coding disabled - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ optimizeCoding: false }) - .toBuffer(function (err, withoutOptimiseCoding, withoutInfo) { - if (err) throw err; - assert.strictEqual(true, withoutOptimiseCoding.length > 0); - assert.strictEqual(withoutOptimiseCoding.length, withoutInfo.size); - assert.strictEqual('jpeg', withoutInfo.format); - assert.strictEqual(320, withoutInfo.width); - assert.strictEqual(240, withoutInfo.height); - // Verify optimised image is of a smaller size - assert.strictEqual(true, withOptimiseCoding.length < withoutOptimiseCoding.length); - done(); - }); - }); - }); - - it('Specifying quantisation table provides different JPEG', function (done) { - // First generate with default quantisation table - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ optimiseCoding: false }) - .toBuffer(function (err, withDefaultQuantisationTable, withInfo) { - if (err) throw err; - assert.strictEqual(true, withDefaultQuantisationTable.length > 0); - assert.strictEqual(withDefaultQuantisationTable.length, withInfo.size); - assert.strictEqual('jpeg', withInfo.format); - assert.strictEqual(320, withInfo.width); - assert.strictEqual(240, withInfo.height); - // Then generate with different quantisation table - sharp(fixtures.inputJpg) - .resize(320, 240) - .jpeg({ optimiseCoding: false, quantisationTable: 3 }) - .toBuffer(function (err, withQuantTable3, withoutInfo) { - if (err) throw err; - assert.strictEqual(true, withQuantTable3.length > 0); - assert.strictEqual(withQuantTable3.length, withoutInfo.size); - assert.strictEqual('jpeg', withoutInfo.format); - assert.strictEqual(320, withoutInfo.width); - assert.strictEqual(240, withoutInfo.height); - - // Verify image is same (as mozjpeg may not be present) size or less - assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantisationTable.length); - done(); - }); - }); - }); - - it('Convert SVG to PNG at default 72DPI', function (done) { - sharp(fixtures.inputSvg) - .resize(1024) - .extract({ left: 290, top: 760, width: 40, height: 40 }) - .toFormat('png') - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(40, info.width); - assert.strictEqual(40, info.height); - fixtures.assertSimilar(fixtures.expected('svg72.png'), data, function (err) { - if (err) throw err; - sharp(data).metadata(function (err, info) { - if (err) throw err; - assert.strictEqual(72, info.density); - done(); - }); - }); - }); - }); - - it('Convert SVG to PNG at 1200DPI', function (done) { - sharp(fixtures.inputSvg, { density: 1200 }) - .resize(1024) - .extract({ left: 290, top: 760, width: 40, height: 40 }) - .toFormat('png') - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(40, info.width); - assert.strictEqual(40, info.height); - fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function (err) { - if (err) throw err; - sharp(data).metadata(function (err, info) { - if (err) throw err; - assert.strictEqual(1200, info.density); - done(); - }); - }); - }); - }); - - it('Convert SVG to PNG at 14.4DPI', function (done) { - sharp(fixtures.inputSvg, { density: 14.4 }) - .toFormat('png') - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(20, info.width); - assert.strictEqual(20, info.height); - fixtures.assertSimilar(fixtures.expected('svg14.4.png'), data, function (err) { - if (err) throw err; - done(); - }); - }); - }); - - it('Convert SVG with embedded images to PNG, respecting dimensions, autoconvert to PNG', function (done) { - sharp(fixtures.inputSvgWithEmbeddedImages) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual('png', info.format); - assert.strictEqual(480, info.width); - assert.strictEqual(360, info.height); - assert.strictEqual(4, info.channels); - fixtures.assertSimilar(fixtures.expected('svg-embedded.png'), data, done); - }); - }); - - it('Load TIFF from Buffer', function (done) { - const inputTiffBuffer = fs.readFileSync(fixtures.inputTiff); - sharp(inputTiffBuffer) - .resize(320, 240) - .jpeg() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual(data.length, info.size); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - done(); - }); - }); - - it('Load multi-page TIFF\'s from file', function (done) { - sharp(fixtures.inputTiffMultipage) // defaults to page 0 - .jpeg() - .toBuffer(function (err, defaultData, defaultInfo) { - if (err) throw err; - assert.strictEqual(true, defaultData.length > 0); - assert.strictEqual(defaultData.length, defaultInfo.size); - assert.strictEqual('jpeg', defaultInfo.format); - - sharp(fixtures.inputTiffMultipage, { page: 1 }) // 50%-scale copy of page 0 - .jpeg() - .toBuffer(function (err, scaledData, scaledInfo) { - if (err) throw err; - assert.strictEqual(true, scaledData.length > 0); - assert.strictEqual(scaledData.length, scaledInfo.size); - assert.strictEqual('jpeg', scaledInfo.format); - assert.strictEqual(defaultInfo.width, scaledInfo.width * 2); - assert.strictEqual(defaultInfo.height, scaledInfo.height * 2); - done(); - }); - }); - }); - - it('Load multi-page TIFF\'s from Buffer', function (done) { - const inputTiffBuffer = fs.readFileSync(fixtures.inputTiffMultipage); - sharp(inputTiffBuffer) // defaults to page 0 - .jpeg() - .toBuffer(function (err, defaultData, defaultInfo) { - if (err) throw err; - assert.strictEqual(true, defaultData.length > 0); - assert.strictEqual(defaultData.length, defaultInfo.size); - assert.strictEqual('jpeg', defaultInfo.format); - - sharp(inputTiffBuffer, { page: 1 }) // 50%-scale copy of page 0 - .jpeg() - .toBuffer(function (err, scaledData, scaledInfo) { - if (err) throw err; - assert.strictEqual(true, scaledData.length > 0); - assert.strictEqual(scaledData.length, scaledInfo.size); - assert.strictEqual('jpeg', scaledInfo.format); - assert.strictEqual(defaultInfo.width, scaledInfo.width * 2); - assert.strictEqual(defaultInfo.height, scaledInfo.height * 2); - done(); - }); - }); - }); - - it('Save TIFF to Buffer', function (done) { - sharp(fixtures.inputTiff) - .resize(320, 240) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual(data.length, info.size); - assert.strictEqual('tiff', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - done(); - }); - }); - - it('Invalid WebP quality throws error', function () { - assert.throws(function () { - sharp().webp({ quality: 101 }); - }); - }); - - it('Invalid WebP alpha quality throws error', function () { - assert.throws(function () { - sharp().webp({ alphaQuality: 101 }); - }); - }); - - it('Invalid TIFF quality throws error', function () { - assert.throws(function () { - sharp().tiff({ quality: 101 }); - }); - }); - - it('Missing TIFF quality does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff(); - }); - }); - - it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) { - const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; - sharp(fixtures.inputTiff8BitDepth) - .toColourspace('b-w') // can only squash 1 band uchar images - .tiff({ - squash: false, - compression: 'none', - predictor: 'none' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size === startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) { - const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; - sharp(fixtures.inputTiff8BitDepth) - .toColourspace('b-w') // can only squash 1 band uchar images - .tiff({ - squash: true, - compression: 'none', - predictor: 'none' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < (startSize / 2)); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('Invalid TIFF squash value throws error', function () { - assert.throws(function () { - sharp().tiff({ squash: 'true' }); - }); - }); - - it('TIFF setting xres and yres on file', function (done) { - const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54) - sharp(fixtures.inputTiff) - .tiff({ - xres: (res), - yres: (res) - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - sharp(fixtures.outputTiff).metadata(function (err, metadata) { - if (err) throw err; - assert.strictEqual(metadata.density, res * 2.54); // convert to dpi - rimraf(fixtures.outputTiff, done); - }); - }); - }); - - it('TIFF setting xres and yres on buffer', function (done) { - const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54) - sharp(fixtures.inputTiff) - .tiff({ - xres: (res), - yres: (res) - }) - .toBuffer(function (err, data, info) { - if (err) throw err; - sharp(data).metadata(function (err, metadata) { - if (err) throw err; - assert.strictEqual(metadata.density, res * 2.54); // convert to dpi - done(); - }); - }); - }); - - it('TIFF invalid xres value should throw an error', function () { - assert.throws(function () { - sharp().tiff({ xres: '1000.0' }); - }); - }); - - it('TIFF invalid yres value should throw an error', function () { - assert.throws(function () { - sharp().tiff({ yres: '1000.0' }); - }); - }); - - it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; - sharp(fixtures.inputTiffUncompressed) - .tiff({ - compression: 'lzw', - predictor: 'horizontal' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF ccittfax4 compression shrinks b-w test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiff).size; - sharp(fixtures.inputTiff) - .toColourspace('b-w') - .tiff({ - squash: true, - compression: 'ccittfax4' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF deflate compression with horizontal predictor shrinks test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; - sharp(fixtures.inputTiffUncompressed) - .tiff({ - compression: 'deflate', - predictor: 'horizontal' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF deflate compression with float predictor shrinks test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; - sharp(fixtures.inputTiffUncompressed) - .tiff({ - compression: 'deflate', - predictor: 'float' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF deflate compression without predictor shrinks test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; - sharp(fixtures.inputTiffUncompressed) - .tiff({ - compression: 'deflate', - predictor: 'none' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF jpeg compression shrinks test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; - sharp(fixtures.inputTiffUncompressed) - .tiff({ - compression: 'jpeg' - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size < startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF none compression does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ compression: 'none' }); - }); - }); - - it('TIFF lzw compression does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ compression: 'lzw' }); - }); - }); - - it('TIFF deflate compression does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ compression: 'deflate' }); - }); - }); - - it('TIFF invalid compression option throws', function () { - assert.throws(function () { - sharp().tiff({ compression: 0 }); - }); - }); - - it('TIFF invalid compression option throws', function () { - assert.throws(function () { - sharp().tiff({ compression: 'a' }); - }); - }); - - it('TIFF invalid predictor option throws', function () { - assert.throws(function () { - sharp().tiff({ predictor: 'a' }); - }); - }); - - it('TIFF horizontal predictor does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ predictor: 'horizontal' }); - }); - }); - - it('TIFF float predictor does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ predictor: 'float' }); - }); - }); - - it('TIFF none predictor does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ predictor: 'none' }); - }); - }); - - it('TIFF tiled pyramid image without compression enlarges test file', function (done) { - const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; - sharp(fixtures.inputTiffUncompressed) - .tiff({ - compression: 'none', - pyramid: true, - tile: true, - tileHeight: 256, - tileWidth: 256 - }) - .toFile(fixtures.outputTiff, (err, info) => { - if (err) throw err; - assert.strictEqual('tiff', info.format); - assert(info.size > startSize); - rimraf(fixtures.outputTiff, done); - }); - }); - - it('TIFF pyramid true value does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ pyramid: true }); - }); - }); - - it('Invalid TIFF pyramid value throws error', function () { - assert.throws(function () { - sharp().tiff({ pyramid: 'true' }); - }); - }); - - it('Invalid TIFF tile value throws error', function () { - assert.throws(function () { - sharp().tiff({ tile: 'true' }); - }); - }); - - it('TIFF tile true value does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ tile: true }); - }); - }); - - it('Valid TIFF tileHeight value does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ tileHeight: 512 }); - }); - }); - - it('Valid TIFF tileWidth value does not throw error', function () { - assert.doesNotThrow(function () { - sharp().tiff({ tileWidth: 512 }); - }); - }); - - it('Invalid TIFF tileHeight value throws error', function () { - assert.throws(function () { - sharp().tiff({ tileHeight: '256' }); - }); - }); - - it('Invalid TIFF tileWidth value throws error', function () { - assert.throws(function () { - sharp().tiff({ tileWidth: '256' }); - }); - }); - - it('Invalid TIFF tileHeight value throws error', function () { - assert.throws(function () { - sharp().tiff({ tileHeight: 0 }); - }); - }); - - it('Invalid TIFF tileWidth value throws error', function () { - assert.throws(function () { - sharp().tiff({ tileWidth: 0 }); - }); - }); - it('Input and output formats match when not forcing', function (done) { sharp(fixtures.inputJpg) .resize(320, 240) @@ -1392,53 +557,6 @@ describe('Input/output', function () { }); }); - describe('Ouput raw, uncompressed image data', function () { - it('1 channel greyscale image', function (done) { - sharp(fixtures.inputJpg) - .greyscale() - .resize(32, 24) - .raw() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(32 * 24 * 1, info.size); - assert.strictEqual(data.length, info.size); - assert.strictEqual('raw', info.format); - assert.strictEqual(32, info.width); - assert.strictEqual(24, info.height); - assert.strictEqual(1, info.channels); - done(); - }); - }); - it('3 channel colour image without transparency', function (done) { - sharp(fixtures.inputJpg) - .resize(32, 24) - .toFormat('raw') - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(32 * 24 * 3, info.size); - assert.strictEqual(data.length, info.size); - assert.strictEqual('raw', info.format); - assert.strictEqual(32, info.width); - assert.strictEqual(24, info.height); - done(); - }); - }); - it('4 channel colour image with transparency', function (done) { - sharp(fixtures.inputPngWithTransparency) - .resize(32, 24) - .toFormat(sharp.format.raw) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(32 * 24 * 4, info.size); - assert.strictEqual(data.length, info.size); - assert.strictEqual('raw', info.format); - assert.strictEqual(32, info.width); - assert.strictEqual(24, info.height); - done(); - }); - }); - }); - describe('Limit pixel count of input image', function () { it('Invalid fails - negative', function (done) { let isValid = false; @@ -1530,88 +648,6 @@ describe('Input/output', function () { }); }); - describe('Raw pixel input', function () { - it('Missing options', function () { - assert.throws(function () { - sharp({ raw: {} }); - }); - }); - it('Incomplete options', function () { - assert.throws(function () { - sharp({ raw: { width: 1, height: 1 } }); - }); - }); - it('Invalid channels', function () { - assert.throws(function () { - sharp({ raw: { width: 1, height: 1, channels: 5 } }); - }); - }); - it('Invalid height', function () { - assert.throws(function () { - sharp({ raw: { width: 1, height: 0, channels: 4 } }); - }); - }); - it('Invalid width', function () { - assert.throws(function () { - sharp({ raw: { width: 'zoinks', height: 1, channels: 4 } }); - }); - }); - it('RGB', function (done) { - // Convert to raw pixel data - sharp(fixtures.inputJpg) - .resize(256) - .raw() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(256, info.width); - assert.strictEqual(209, info.height); - assert.strictEqual(3, info.channels); - // Convert back to JPEG - sharp(data, { - raw: { - width: info.width, - height: info.height, - channels: info.channels - } }) - .jpeg() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(256, info.width); - assert.strictEqual(209, info.height); - assert.strictEqual(3, info.channels); - fixtures.assertSimilar(fixtures.inputJpg, data, done); - }); - }); - }); - it('RGBA', function (done) { - // Convert to raw pixel data - sharp(fixtures.inputPngOverlayLayer1) - .resize(256) - .raw() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(256, info.width); - assert.strictEqual(192, info.height); - assert.strictEqual(4, info.channels); - // Convert back to PNG - sharp(data, { - raw: { - width: info.width, - height: info.height, - channels: info.channels - } }) - .png() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(256, info.width); - assert.strictEqual(192, info.height); - assert.strictEqual(4, info.channels); - fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, { threshold: 7 }, done); - }); - }); - }); - }); - describe('create new image', function () { it('RGB', function (done) { const create = { diff --git a/test/unit/jpeg.js b/test/unit/jpeg.js new file mode 100644 index 00000000..041ccb37 --- /dev/null +++ b/test/unit/jpeg.js @@ -0,0 +1,262 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('JPEG', function () { + it('JPEG quality', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ quality: 70 }) + .toBuffer(function (err, buffer70) { + if (err) throw err; + sharp(fixtures.inputJpg) + .resize(320, 240) + .toBuffer(function (err, buffer80) { + if (err) throw err; + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ quality: 90 }) + .toBuffer(function (err, buffer90) { + if (err) throw err; + assert(buffer70.length < buffer80.length); + assert(buffer80.length < buffer90.length); + done(); + }); + }); + }); + }); + + describe('Invalid JPEG quality', function () { + [-1, 88.2, 'test'].forEach(function (quality) { + it(quality.toString(), function () { + assert.throws(function () { + sharp().jpeg({ quality: quality }); + }); + }); + }); + }); + + describe('Invalid JPEG quantisation table', function () { + [-1, 88.2, 'test'].forEach(function (table) { + it(table.toString(), function () { + assert.throws(function () { + sharp().jpeg({ quantisationTable: table }); + }); + }); + }); + }); + + it('Progressive JPEG image', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ 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('jpeg', nonProgressiveInfo.format); + assert.strictEqual(320, nonProgressiveInfo.width); + assert.strictEqual(240, nonProgressiveInfo.height); + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ 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(false, progressiveData.length === nonProgressiveData.length); + assert.strictEqual('jpeg', progressiveInfo.format); + assert.strictEqual(320, progressiveInfo.width); + assert.strictEqual(240, progressiveInfo.height); + done(); + }); + }); + }); + + it('Without chroma subsampling generates larger file', function (done) { + // First generate with chroma subsampling (default) + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ chromaSubsampling: '4:2:0' }) + .toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) { + if (err) throw err; + assert.strictEqual(true, withChromaSubsamplingData.length > 0); + assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size); + assert.strictEqual('jpeg', withChromaSubsamplingInfo.format); + assert.strictEqual(320, withChromaSubsamplingInfo.width); + assert.strictEqual(240, withChromaSubsamplingInfo.height); + // Then generate without + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ chromaSubsampling: '4:4:4' }) + .toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) { + if (err) throw err; + assert.strictEqual(true, withoutChromaSubsamplingData.length > 0); + assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size); + assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format); + assert.strictEqual(320, withoutChromaSubsamplingInfo.width); + assert.strictEqual(240, withoutChromaSubsamplingInfo.height); + assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length); + done(); + }); + }); + }); + + it('Invalid JPEG chromaSubsampling value throws error', function () { + assert.throws(function () { + sharp().jpeg({ chromaSubsampling: '4:2:2' }); + }); + }); + + it('Trellis quantisation', function (done) { + // First generate without + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ trellisQuantisation: false }) + .toBuffer(function (err, withoutData, withoutInfo) { + if (err) throw err; + assert.strictEqual(true, withoutData.length > 0); + assert.strictEqual(withoutData.length, withoutInfo.size); + assert.strictEqual('jpeg', withoutInfo.format); + assert.strictEqual(320, withoutInfo.width); + assert.strictEqual(240, withoutInfo.height); + // Then generate with + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ trellisQuantization: true }) + .toBuffer(function (err, withData, withInfo) { + if (err) throw err; + assert.strictEqual(true, withData.length > 0); + assert.strictEqual(withData.length, withInfo.size); + assert.strictEqual('jpeg', withInfo.format); + assert.strictEqual(320, withInfo.width); + assert.strictEqual(240, withInfo.height); + // Verify image is same (as mozjpeg may not be present) size or less + assert.strictEqual(true, withData.length <= withoutData.length); + done(); + }); + }); + }); + + it('Overshoot deringing', function (done) { + // First generate without + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ overshootDeringing: false }) + .toBuffer(function (err, withoutData, withoutInfo) { + if (err) throw err; + assert.strictEqual(true, withoutData.length > 0); + assert.strictEqual(withoutData.length, withoutInfo.size); + assert.strictEqual('jpeg', withoutInfo.format); + assert.strictEqual(320, withoutInfo.width); + assert.strictEqual(240, withoutInfo.height); + // Then generate with + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ overshootDeringing: true }) + .toBuffer(function (err, withData, withInfo) { + if (err) throw err; + assert.strictEqual(true, withData.length > 0); + assert.strictEqual(withData.length, withInfo.size); + assert.strictEqual('jpeg', withInfo.format); + assert.strictEqual(320, withInfo.width); + assert.strictEqual(240, withInfo.height); + done(); + }); + }); + }); + + it('Optimise scans generates different output length', function (done) { + // First generate without + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ optimiseScans: false }) + .toBuffer(function (err, withoutData, withoutInfo) { + if (err) throw err; + assert.strictEqual(true, withoutData.length > 0); + assert.strictEqual(withoutData.length, withoutInfo.size); + assert.strictEqual('jpeg', withoutInfo.format); + assert.strictEqual(320, withoutInfo.width); + assert.strictEqual(240, withoutInfo.height); + // Then generate with + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ optimizeScans: true }) + .toBuffer(function (err, withData, withInfo) { + if (err) throw err; + assert.strictEqual(true, withData.length > 0); + assert.strictEqual(withData.length, withInfo.size); + assert.strictEqual('jpeg', withInfo.format); + assert.strictEqual(320, withInfo.width); + assert.strictEqual(240, withInfo.height); + // Verify image is of a different size (progressive output even without mozjpeg) + assert.notStrictEqual(withData.length, withoutData.length); + done(); + }); + }); + }); + + it('Optimise coding generates smaller output length', function (done) { + // First generate with optimize coding enabled (default) + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg() + .toBuffer(function (err, withOptimiseCoding, withInfo) { + if (err) throw err; + assert.strictEqual(true, withOptimiseCoding.length > 0); + assert.strictEqual(withOptimiseCoding.length, withInfo.size); + assert.strictEqual('jpeg', withInfo.format); + assert.strictEqual(320, withInfo.width); + assert.strictEqual(240, withInfo.height); + // Then generate with coding disabled + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ optimizeCoding: false }) + .toBuffer(function (err, withoutOptimiseCoding, withoutInfo) { + if (err) throw err; + assert.strictEqual(true, withoutOptimiseCoding.length > 0); + assert.strictEqual(withoutOptimiseCoding.length, withoutInfo.size); + assert.strictEqual('jpeg', withoutInfo.format); + assert.strictEqual(320, withoutInfo.width); + assert.strictEqual(240, withoutInfo.height); + // Verify optimised image is of a smaller size + assert.strictEqual(true, withOptimiseCoding.length < withoutOptimiseCoding.length); + done(); + }); + }); + }); + + it('Specifying quantisation table provides different JPEG', function (done) { + // First generate with default quantisation table + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ optimiseCoding: false }) + .toBuffer(function (err, withDefaultQuantisationTable, withInfo) { + if (err) throw err; + assert.strictEqual(true, withDefaultQuantisationTable.length > 0); + assert.strictEqual(withDefaultQuantisationTable.length, withInfo.size); + assert.strictEqual('jpeg', withInfo.format); + assert.strictEqual(320, withInfo.width); + assert.strictEqual(240, withInfo.height); + // Then generate with different quantisation table + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ optimiseCoding: false, quantisationTable: 3 }) + .toBuffer(function (err, withQuantTable3, withoutInfo) { + if (err) throw err; + assert.strictEqual(true, withQuantTable3.length > 0); + assert.strictEqual(withQuantTable3.length, withoutInfo.size); + assert.strictEqual('jpeg', withoutInfo.format); + assert.strictEqual(320, withoutInfo.width); + assert.strictEqual(240, withoutInfo.height); + + // Verify image is same (as mozjpeg may not be present) size or less + assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantisationTable.length); + done(); + }); + }); + }); +}); diff --git a/test/unit/png.js b/test/unit/png.js index 652b8b7b..d3c642b6 100644 --- a/test/unit/png.js +++ b/test/unit/png.js @@ -6,7 +6,7 @@ const assert = require('assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('PNG output', function () { +describe('PNG', function () { it('compression level is valid', function () { assert.doesNotThrow(function () { sharp().png({ compressionLevel: 0 }); @@ -77,6 +77,32 @@ describe('PNG output', function () { }); }); + 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('Valid PNG libimagequant palette value does not throw error', function () { assert.doesNotThrow(function () { sharp().png({ palette: false }); diff --git a/test/unit/raw.js b/test/unit/raw.js new file mode 100644 index 00000000..e4c64f48 --- /dev/null +++ b/test/unit/raw.js @@ -0,0 +1,145 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('Raw pixel data', function () { + describe('Raw pixel input', function () { + it('Missing options', function () { + assert.throws(function () { + sharp({ raw: {} }); + }); + }); + + it('Incomplete options', function () { + assert.throws(function () { + sharp({ raw: { width: 1, height: 1 } }); + }); + }); + + it('Invalid channels', function () { + assert.throws(function () { + sharp({ raw: { width: 1, height: 1, channels: 5 } }); + }); + }); + + it('Invalid height', function () { + assert.throws(function () { + sharp({ raw: { width: 1, height: 0, channels: 4 } }); + }); + }); + + it('Invalid width', function () { + assert.throws(function () { + sharp({ raw: { width: 'zoinks', height: 1, channels: 4 } }); + }); + }); + + it('RGB', function (done) { + // Convert to raw pixel data + sharp(fixtures.inputJpg) + .resize(256) + .raw() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(256, info.width); + assert.strictEqual(209, info.height); + assert.strictEqual(3, info.channels); + // Convert back to JPEG + sharp(data, { + raw: { + width: info.width, + height: info.height, + channels: info.channels + } }) + .jpeg() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(256, info.width); + assert.strictEqual(209, info.height); + assert.strictEqual(3, info.channels); + fixtures.assertSimilar(fixtures.inputJpg, data, done); + }); + }); + }); + + it('RGBA', function (done) { + // Convert to raw pixel data + sharp(fixtures.inputPngOverlayLayer1) + .resize(256) + .raw() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(256, info.width); + assert.strictEqual(192, info.height); + assert.strictEqual(4, info.channels); + // Convert back to PNG + sharp(data, { + raw: { + width: info.width, + height: info.height, + channels: info.channels + } }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(256, info.width); + assert.strictEqual(192, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, { threshold: 7 }, done); + }); + }); + }); + }); + + describe('Ouput raw, uncompressed image data', function () { + it('1 channel greyscale image', function (done) { + sharp(fixtures.inputJpg) + .greyscale() + .resize(32, 24) + .raw() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(32 * 24 * 1, info.size); + assert.strictEqual(data.length, info.size); + assert.strictEqual('raw', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(24, info.height); + assert.strictEqual(1, info.channels); + done(); + }); + }); + + it('3 channel colour image without transparency', function (done) { + sharp(fixtures.inputJpg) + .resize(32, 24) + .toFormat('raw') + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(32 * 24 * 3, info.size); + assert.strictEqual(data.length, info.size); + assert.strictEqual('raw', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(24, info.height); + done(); + }); + }); + + it('4 channel colour image with transparency', function (done) { + sharp(fixtures.inputPngWithTransparency) + .resize(32, 24) + .toFormat(sharp.format.raw) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(32 * 24 * 4, info.size); + assert.strictEqual(data.length, info.size); + assert.strictEqual('raw', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(24, info.height); + done(); + }); + }); + }); +}); diff --git a/test/unit/svg.js b/test/unit/svg.js new file mode 100644 index 00000000..afeff85f --- /dev/null +++ b/test/unit/svg.js @@ -0,0 +1,77 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('SVG input', function () { + it('Convert SVG to PNG at default 72DPI', function (done) { + sharp(fixtures.inputSvg) + .resize(1024) + .extract({ left: 290, top: 760, width: 40, height: 40 }) + .toFormat('png') + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(40, info.width); + assert.strictEqual(40, info.height); + fixtures.assertSimilar(fixtures.expected('svg72.png'), data, function (err) { + if (err) throw err; + sharp(data).metadata(function (err, info) { + if (err) throw err; + assert.strictEqual(72, info.density); + done(); + }); + }); + }); + }); + + it('Convert SVG to PNG at 1200DPI', function (done) { + sharp(fixtures.inputSvg, { density: 1200 }) + .resize(1024) + .extract({ left: 290, top: 760, width: 40, height: 40 }) + .toFormat('png') + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(40, info.width); + assert.strictEqual(40, info.height); + fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function (err) { + if (err) throw err; + sharp(data).metadata(function (err, info) { + if (err) throw err; + assert.strictEqual(1200, info.density); + done(); + }); + }); + }); + }); + + it('Convert SVG to PNG at 14.4DPI', function (done) { + sharp(fixtures.inputSvg, { density: 14.4 }) + .toFormat('png') + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(20, info.width); + assert.strictEqual(20, info.height); + fixtures.assertSimilar(fixtures.expected('svg14.4.png'), data, function (err) { + if (err) throw err; + done(); + }); + }); + }); + + it('Convert SVG with embedded images to PNG, respecting dimensions, autoconvert to PNG', function (done) { + sharp(fixtures.inputSvgWithEmbeddedImages) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(480, info.width); + assert.strictEqual(360, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('svg-embedded.png'), data, done); + }); + }); +}); diff --git a/test/unit/tiff.js b/test/unit/tiff.js new file mode 100644 index 00000000..c0ababb0 --- /dev/null +++ b/test/unit/tiff.js @@ -0,0 +1,424 @@ +'use strict'; + +const fs = require('fs'); +const assert = require('assert'); +const rimraf = require('rimraf'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('TIFF', function () { + it('Load TIFF from Buffer', function (done) { + const inputTiffBuffer = fs.readFileSync(fixtures.inputTiff); + sharp(inputTiffBuffer) + .resize(320, 240) + .jpeg() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + done(); + }); + }); + + it('Load multi-page TIFF from file', function (done) { + sharp(fixtures.inputTiffMultipage) // defaults to page 0 + .jpeg() + .toBuffer(function (err, defaultData, defaultInfo) { + if (err) throw err; + assert.strictEqual(true, defaultData.length > 0); + assert.strictEqual(defaultData.length, defaultInfo.size); + assert.strictEqual('jpeg', defaultInfo.format); + + sharp(fixtures.inputTiffMultipage, { page: 1 }) // 50%-scale copy of page 0 + .jpeg() + .toBuffer(function (err, scaledData, scaledInfo) { + if (err) throw err; + assert.strictEqual(true, scaledData.length > 0); + assert.strictEqual(scaledData.length, scaledInfo.size); + assert.strictEqual('jpeg', scaledInfo.format); + assert.strictEqual(defaultInfo.width, scaledInfo.width * 2); + assert.strictEqual(defaultInfo.height, scaledInfo.height * 2); + done(); + }); + }); + }); + + it('Load multi-page TIFF from Buffer', function (done) { + const inputTiffBuffer = fs.readFileSync(fixtures.inputTiffMultipage); + sharp(inputTiffBuffer) // defaults to page 0 + .jpeg() + .toBuffer(function (err, defaultData, defaultInfo) { + if (err) throw err; + assert.strictEqual(true, defaultData.length > 0); + assert.strictEqual(defaultData.length, defaultInfo.size); + assert.strictEqual('jpeg', defaultInfo.format); + + sharp(inputTiffBuffer, { page: 1 }) // 50%-scale copy of page 0 + .jpeg() + .toBuffer(function (err, scaledData, scaledInfo) { + if (err) throw err; + assert.strictEqual(true, scaledData.length > 0); + assert.strictEqual(scaledData.length, scaledInfo.size); + assert.strictEqual('jpeg', scaledInfo.format); + assert.strictEqual(defaultInfo.width, scaledInfo.width * 2); + assert.strictEqual(defaultInfo.height, scaledInfo.height * 2); + done(); + }); + }); + }); + + it('Save TIFF to Buffer', function (done) { + sharp(fixtures.inputTiff) + .resize(320, 240) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('tiff', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + done(); + }); + }); + + it('Invalid TIFF quality throws error', function () { + assert.throws(function () { + sharp().tiff({ quality: 101 }); + }); + }); + + it('Missing TIFF quality does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff(); + }); + }); + + it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) { + const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; + sharp(fixtures.inputTiff8BitDepth) + .toColourspace('b-w') // can only squash 1 band uchar images + .tiff({ + squash: false, + compression: 'none', + predictor: 'none' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size === startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) { + const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size; + sharp(fixtures.inputTiff8BitDepth) + .toColourspace('b-w') // can only squash 1 band uchar images + .tiff({ + squash: true, + compression: 'none', + predictor: 'none' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < (startSize / 2)); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('Invalid TIFF squash value throws error', function () { + assert.throws(function () { + sharp().tiff({ squash: 'true' }); + }); + }); + + it('TIFF setting xres and yres on file', function (done) { + const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54) + sharp(fixtures.inputTiff) + .tiff({ + xres: (res), + yres: (res) + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + sharp(fixtures.outputTiff).metadata(function (err, metadata) { + if (err) throw err; + assert.strictEqual(metadata.density, res * 2.54); // convert to dpi + rimraf(fixtures.outputTiff, done); + }); + }); + }); + + it('TIFF setting xres and yres on buffer', function (done) { + const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54) + sharp(fixtures.inputTiff) + .tiff({ + xres: (res), + yres: (res) + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + sharp(data).metadata(function (err, metadata) { + if (err) throw err; + assert.strictEqual(metadata.density, res * 2.54); // convert to dpi + done(); + }); + }); + }); + + it('TIFF invalid xres value should throw an error', function () { + assert.throws(function () { + sharp().tiff({ xres: '1000.0' }); + }); + }); + + it('TIFF invalid yres value should throw an error', function () { + assert.throws(function () { + sharp().tiff({ yres: '1000.0' }); + }); + }); + + it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; + sharp(fixtures.inputTiffUncompressed) + .tiff({ + compression: 'lzw', + predictor: 'horizontal' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF ccittfax4 compression shrinks b-w test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiff).size; + sharp(fixtures.inputTiff) + .toColourspace('b-w') + .tiff({ + squash: true, + compression: 'ccittfax4' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF deflate compression with horizontal predictor shrinks test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; + sharp(fixtures.inputTiffUncompressed) + .tiff({ + compression: 'deflate', + predictor: 'horizontal' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF deflate compression with float predictor shrinks test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; + sharp(fixtures.inputTiffUncompressed) + .tiff({ + compression: 'deflate', + predictor: 'float' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF deflate compression without predictor shrinks test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; + sharp(fixtures.inputTiffUncompressed) + .tiff({ + compression: 'deflate', + predictor: 'none' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF jpeg compression shrinks test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; + sharp(fixtures.inputTiffUncompressed) + .tiff({ + compression: 'jpeg' + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size < startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF none compression does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ compression: 'none' }); + }); + }); + + it('TIFF lzw compression does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ compression: 'lzw' }); + }); + }); + + it('TIFF deflate compression does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ compression: 'deflate' }); + }); + }); + + it('TIFF invalid compression option throws', function () { + assert.throws(function () { + sharp().tiff({ compression: 0 }); + }); + }); + + it('TIFF invalid compression option throws', function () { + assert.throws(function () { + sharp().tiff({ compression: 'a' }); + }); + }); + + it('TIFF invalid predictor option throws', function () { + assert.throws(function () { + sharp().tiff({ predictor: 'a' }); + }); + }); + + it('TIFF horizontal predictor does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ predictor: 'horizontal' }); + }); + }); + + it('TIFF float predictor does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ predictor: 'float' }); + }); + }); + + it('TIFF none predictor does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ predictor: 'none' }); + }); + }); + + it('TIFF tiled pyramid image without compression enlarges test file', function (done) { + const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; + sharp(fixtures.inputTiffUncompressed) + .tiff({ + compression: 'none', + pyramid: true, + tile: true, + tileHeight: 256, + tileWidth: 256 + }) + .toFile(fixtures.outputTiff, (err, info) => { + if (err) throw err; + assert.strictEqual('tiff', info.format); + assert(info.size > startSize); + rimraf(fixtures.outputTiff, done); + }); + }); + + it('TIFF pyramid true value does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ pyramid: true }); + }); + }); + + it('Invalid TIFF pyramid value throws error', function () { + assert.throws(function () { + sharp().tiff({ pyramid: 'true' }); + }); + }); + + it('Invalid TIFF tile value throws error', function () { + assert.throws(function () { + sharp().tiff({ tile: 'true' }); + }); + }); + + it('TIFF tile true value does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ tile: true }); + }); + }); + + it('Valid TIFF tileHeight value does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ tileHeight: 512 }); + }); + }); + + it('Valid TIFF tileWidth value does not throw error', function () { + assert.doesNotThrow(function () { + sharp().tiff({ tileWidth: 512 }); + }); + }); + + it('Invalid TIFF tileHeight value throws error', function () { + assert.throws(function () { + sharp().tiff({ tileHeight: '256' }); + }); + }); + + it('Invalid TIFF tileWidth value throws error', function () { + assert.throws(function () { + sharp().tiff({ tileWidth: '256' }); + }); + }); + + it('Invalid TIFF tileHeight value throws error', function () { + assert.throws(function () { + sharp().tiff({ tileHeight: 0 }); + }); + }); + + it('Invalid TIFF tileWidth value throws error', function () { + assert.throws(function () { + sharp().tiff({ tileWidth: 0 }); + }); + }); + + it('TIFF file input with invalid page fails gracefully', function (done) { + sharp(fixtures.inputTiffMultipage, { page: 2 }) + .toBuffer(function (err) { + assert.strictEqual(true, !!err); + done(); + }); + }); + + it('TIFF buffer input with invalid page fails gracefully', function (done) { + sharp(fs.readFileSync(fixtures.inputTiffMultipage), { page: 2 }) + .toBuffer(function (err) { + assert.strictEqual(true, !!err); + done(); + }); + }); +}); diff --git a/test/unit/webp.js b/test/unit/webp.js new file mode 100644 index 00000000..e6705956 --- /dev/null +++ b/test/unit/webp.js @@ -0,0 +1,78 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('WebP', function () { + it('WebP output', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240) + .toFormat(sharp.format.webp) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + done(); + }); + }); + + it('Invalid WebP quality throws error', function () { + assert.throws(function () { + sharp().webp({ quality: 101 }); + }); + }); + + it('Invalid WebP alpha quality throws error', function () { + assert.throws(function () { + sharp().webp({ alphaQuality: 101 }); + }); + }); + + it('should work for webp alpha quality', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({ alphaQuality: 80 }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + fixtures.assertSimilar(fixtures.expected('webp-alpha-80.webp'), data, done); + }); + }); + + it('should work for webp lossless', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({ lossless: true }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + fixtures.assertSimilar(fixtures.expected('webp-lossless.webp'), data, done); + }); + }); + + it('should work for webp near-lossless', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({ nearLossless: true, quality: 50 }) + .toBuffer(function (err50, data50, info50) { + if (err50) throw err50; + assert.strictEqual(true, data50.length > 0); + assert.strictEqual('webp', info50.format); + fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done); + }); + }); + + it('should use near-lossless when both lossless and nearLossless are specified', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({ nearLossless: true, quality: 50, lossless: true }) + .toBuffer(function (err50, data50, info50) { + if (err50) throw err50; + assert.strictEqual(true, data50.length > 0); + assert.strictEqual('webp', info50.format); + fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done); + }); + }); +});