mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
278 lines
8.9 KiB
JavaScript
278 lines
8.9 KiB
JavaScript
// 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('GIF input', () => {
|
|
it('GIF Buffer to JPEG Buffer', () =>
|
|
sharp(fs.readFileSync(fixtures.inputGif))
|
|
.resize(8, 4)
|
|
.jpeg()
|
|
.toBuffer({ resolveWithObject: true })
|
|
.then(({ data, info }) => {
|
|
assert.strictEqual(true, data.length > 0);
|
|
assert.strictEqual(data.length, info.size);
|
|
assert.strictEqual('jpeg', info.format);
|
|
assert.strictEqual(8, info.width);
|
|
assert.strictEqual(4, info.height);
|
|
})
|
|
);
|
|
|
|
it('2 channel GIF file to PNG Buffer', () =>
|
|
sharp(fixtures.inputGifGreyPlusAlpha)
|
|
.resize(8, 4)
|
|
.png()
|
|
.toBuffer({ resolveWithObject: true })
|
|
.then(({ data, info }) => {
|
|
assert.strictEqual(true, data.length > 0);
|
|
assert.strictEqual(data.length, info.size);
|
|
assert.strictEqual('png', info.format);
|
|
assert.strictEqual(8, info.width);
|
|
assert.strictEqual(4, info.height);
|
|
assert.strictEqual(4, info.channels);
|
|
})
|
|
);
|
|
|
|
it('Animated GIF first page to non-animated GIF', () =>
|
|
sharp(fixtures.inputGifAnimated)
|
|
.toBuffer({ resolveWithObject: true })
|
|
.then(({ data, info }) => {
|
|
assert.strictEqual(true, data.length > 0);
|
|
assert.strictEqual(data.length, info.size);
|
|
assert.strictEqual('gif', info.format);
|
|
assert.strictEqual(80, info.width);
|
|
assert.strictEqual(80, info.height);
|
|
assert.strictEqual(4, info.channels);
|
|
assert.strictEqual(undefined, info.pages);
|
|
assert.strictEqual(undefined, info.pageHeight);
|
|
})
|
|
);
|
|
|
|
it('Animated GIF round trip', () =>
|
|
sharp(fixtures.inputGifAnimated, { pages: -1 })
|
|
.toBuffer({ resolveWithObject: true })
|
|
.then(({ data, info }) => {
|
|
assert.strictEqual(true, data.length > 0);
|
|
assert.strictEqual(data.length, info.size);
|
|
assert.strictEqual('gif', info.format);
|
|
assert.strictEqual(80, info.width);
|
|
assert.strictEqual(2400, info.height);
|
|
assert.strictEqual(4, info.channels);
|
|
assert.strictEqual(30, info.pages);
|
|
assert.strictEqual(80, info.pageHeight);
|
|
})
|
|
);
|
|
|
|
it('GIF with reduced colours, no dither, low effort reduces file size', async () => {
|
|
const original = await sharp(fixtures.inputJpg)
|
|
.resize(120, 80)
|
|
.gif()
|
|
.toBuffer();
|
|
|
|
const reduced = await sharp(fixtures.inputJpg)
|
|
.resize(120, 80)
|
|
.gif({
|
|
colours: 128,
|
|
dither: 0,
|
|
effort: 1
|
|
})
|
|
.toBuffer();
|
|
|
|
assert.strictEqual(true, reduced.length < original.length);
|
|
});
|
|
|
|
it('valid reuse', () => {
|
|
assert.doesNotThrow(() => sharp().gif({ reuse: true }));
|
|
assert.doesNotThrow(() => sharp().gif({ reuse: false }));
|
|
});
|
|
|
|
it('invalid reuse throws', () => {
|
|
assert.throws(
|
|
() => sharp().gif({ reuse: -1 }),
|
|
/Expected boolean for gifReuse but received -1 of type number/
|
|
);
|
|
assert.throws(
|
|
() => sharp().gif({ reuse: 'fail' }),
|
|
/Expected boolean for gifReuse but received fail of type string/
|
|
);
|
|
});
|
|
|
|
it('progressive changes file size', async () => {
|
|
const nonProgressive = await sharp(fixtures.inputGif).gif({ progressive: false }).toBuffer();
|
|
const progressive = await sharp(fixtures.inputGif).gif({ progressive: true }).toBuffer();
|
|
assert(nonProgressive.length !== progressive.length);
|
|
});
|
|
|
|
it('invalid progressive throws', () => {
|
|
assert.throws(
|
|
() => sharp().gif({ progressive: -1 }),
|
|
/Expected boolean for gifProgressive but received -1 of type number/
|
|
);
|
|
assert.throws(
|
|
() => sharp().gif({ progressive: 'fail' }),
|
|
/Expected boolean for gifProgressive but received fail of type string/
|
|
);
|
|
});
|
|
|
|
it('invalid loop throws', () => {
|
|
assert.throws(() => {
|
|
sharp().gif({ loop: -1 });
|
|
});
|
|
assert.throws(() => {
|
|
sharp().gif({ loop: 65536 });
|
|
});
|
|
});
|
|
|
|
it('invalid delay throws', () => {
|
|
assert.throws(() => {
|
|
sharp().gif({ delay: -1 });
|
|
});
|
|
assert.throws(() => {
|
|
sharp().gif({ delay: [65536] });
|
|
});
|
|
});
|
|
|
|
it('invalid colour throws', () => {
|
|
assert.throws(() => {
|
|
sharp().gif({ colours: 1 });
|
|
});
|
|
assert.throws(() => {
|
|
sharp().gif({ colours: 'fail' });
|
|
});
|
|
});
|
|
|
|
it('invalid effort throws', () => {
|
|
assert.throws(() => {
|
|
sharp().gif({ effort: 0 });
|
|
});
|
|
assert.throws(() => {
|
|
sharp().gif({ effort: 'fail' });
|
|
});
|
|
});
|
|
|
|
it('invalid dither throws', () => {
|
|
assert.throws(() => {
|
|
sharp().gif({ dither: 1.1 });
|
|
});
|
|
assert.throws(() => {
|
|
sharp().gif({ effort: 'fail' });
|
|
});
|
|
});
|
|
|
|
it('invalid interFrameMaxError throws', () => {
|
|
assert.throws(
|
|
() => sharp().gif({ interFrameMaxError: 33 }),
|
|
/Expected number between 0.0 and 32.0 for interFrameMaxError but received 33 of type number/
|
|
);
|
|
assert.throws(
|
|
() => sharp().gif({ interFrameMaxError: 'fail' }),
|
|
/Expected number between 0.0 and 32.0 for interFrameMaxError but received fail of type string/
|
|
);
|
|
});
|
|
|
|
it('invalid interPaletteMaxError throws', () => {
|
|
assert.throws(
|
|
() => sharp().gif({ interPaletteMaxError: 257 }),
|
|
/Expected number between 0.0 and 256.0 for interPaletteMaxError but received 257 of type number/
|
|
);
|
|
assert.throws(
|
|
() => sharp().gif({ interPaletteMaxError: 'fail' }),
|
|
/Expected number between 0.0 and 256.0 for interPaletteMaxError but received fail of type string/
|
|
);
|
|
});
|
|
|
|
it('invalid keepDuplicateFrames throws', () => {
|
|
assert.throws(
|
|
() => sharp().gif({ keepDuplicateFrames: -1 }),
|
|
/Expected boolean for keepDuplicateFrames but received -1 of type number/
|
|
);
|
|
assert.throws(
|
|
() => sharp().gif({ keepDuplicateFrames: 'fail' }),
|
|
/Expected boolean for keepDuplicateFrames but received fail of type string/
|
|
);
|
|
});
|
|
|
|
it('should work with streams when only animated is set', function (done) {
|
|
fs.createReadStream(fixtures.inputGifAnimated)
|
|
.pipe(sharp({ animated: true }))
|
|
.gif()
|
|
.toBuffer(function (err, data, info) {
|
|
if (err) throw err;
|
|
assert.strictEqual(true, data.length > 0);
|
|
assert.strictEqual('gif', info.format);
|
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
|
});
|
|
});
|
|
|
|
it('should work with streams when only pages is set', function (done) {
|
|
fs.createReadStream(fixtures.inputGifAnimated)
|
|
.pipe(sharp({ pages: -1 }))
|
|
.gif()
|
|
.toBuffer(function (err, data, info) {
|
|
if (err) throw err;
|
|
assert.strictEqual(true, data.length > 0);
|
|
assert.strictEqual('gif', info.format);
|
|
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
|
|
});
|
|
});
|
|
|
|
it('should optimise file size via interFrameMaxError', async () => {
|
|
const input = sharp(fixtures.inputGifAnimated, { animated: true });
|
|
const before = await input.gif({ interFrameMaxError: 0 }).toBuffer();
|
|
const after = await input.gif({ interFrameMaxError: 10 }).toBuffer();
|
|
assert.strict(before.length > after.length);
|
|
});
|
|
|
|
it('should optimise file size via interPaletteMaxError', async () => {
|
|
const input = sharp(fixtures.inputGifAnimated, { animated: true });
|
|
const before = await input.gif({ interPaletteMaxError: 0 }).toBuffer();
|
|
const after = await input.gif({ interPaletteMaxError: 100 }).toBuffer();
|
|
assert.strict(before.length > after.length);
|
|
});
|
|
|
|
it('should keep duplicate frames via keepDuplicateFrames', async () => {
|
|
const create = { width: 8, height: 8, channels: 4, background: 'blue' };
|
|
const input = sharp([{ create }, { create }], { join: { animated: true } });
|
|
|
|
const before = await input.gif({ keepDuplicateFrames: false }).toBuffer();
|
|
const after = await input.gif({ keepDuplicateFrames: true }).toBuffer();
|
|
assert.strict(before.length < after.length);
|
|
|
|
const beforeMeta = await sharp(before).metadata();
|
|
const afterMeta = await sharp(after).metadata();
|
|
assert.strictEqual(beforeMeta.pages, 1);
|
|
assert.strictEqual(afterMeta.pages, 2);
|
|
});
|
|
|
|
it('non-animated input defaults to no-loop', async () => {
|
|
for (const input of [fixtures.inputGif, fixtures.inputPng]) {
|
|
const data = await sharp(input)
|
|
.resize(8)
|
|
.gif({ effort: 1 })
|
|
.toBuffer();
|
|
|
|
const { format, pages, loop } = await sharp(data).metadata();
|
|
assert.strictEqual('gif', format);
|
|
assert.strictEqual(1, pages);
|
|
assert.strictEqual(1, loop);
|
|
}
|
|
});
|
|
|
|
it('Animated GIF to animated WebP merges identical frames', async () => {
|
|
const webp = await sharp(fixtures.inputGifAnimated, { animated: true })
|
|
.webp()
|
|
.toBuffer();
|
|
|
|
const { delay, loop, pages } = await sharp(webp).metadata();
|
|
assert.deepStrictEqual([120, 120, 90, 120, 120, 90, 120, 90, 30], delay);
|
|
assert.strictEqual(0, loop);
|
|
assert.strictEqual(9, pages);
|
|
});
|
|
});
|