加入awebp编码支持
This commit is contained in:
1 parent
eb2a7f7d91
commit
081d06c70e
22 files changed
+392
-11
No files matched your search
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
function writeUint24LE(buffer, offset, value) {
|
||||
buffer[offset] = value & 0xff;
|
||||
buffer[offset + 1] = (value >>> 8) & 0xff;
|
||||
buffer[offset + 2] = (value >>> 16) & 0xff;
|
||||
}
|
||||
|
||||
function chunk(type, payload) {
|
||||
const padding = payload.length & 1;
|
||||
const output = Buffer.alloc(8 + payload.length + padding);
|
||||
output.write(type, 0, 4, 'ascii');
|
||||
output.writeUInt32LE(payload.length, 4);
|
||||
payload.copy(output, 8);
|
||||
return output;
|
||||
}
|
||||
|
||||
function frameData(webp) {
|
||||
const input = Buffer.from(webp);
|
||||
if (input.length < 20 || input.toString('ascii', 0, 4) !== 'RIFF' || input.toString('ascii', 8, 12) !== 'WEBP') {
|
||||
throw new Error('Invalid WebP frame');
|
||||
}
|
||||
const parts = [];
|
||||
let hasAlpha = false;
|
||||
let offset = 12;
|
||||
while (offset + 8 <= input.length) {
|
||||
const type = input.toString('ascii', offset, offset + 4);
|
||||
const length = input.readUInt32LE(offset + 4);
|
||||
const end = offset + 8 + length + (length & 1);
|
||||
if (end > input.length) throw new Error('Truncated WebP frame');
|
||||
if (type === 'ALPH' || type === 'VP8 ' || type === 'VP8L') parts.push(input.subarray(offset, end));
|
||||
if (type === 'ALPH') hasAlpha = true;
|
||||
offset = end;
|
||||
}
|
||||
if (!parts.some((part) => part.toString('ascii', 0, 4) === 'VP8 ' || part.toString('ascii', 0, 4) === 'VP8L')) {
|
||||
throw new Error('WebP frame has no image data');
|
||||
}
|
||||
return { data: Buffer.concat(parts), hasAlpha };
|
||||
}
|
||||
|
||||
function encode(options) {
|
||||
const width = Number(options && options.width);
|
||||
const height = Number(options && options.height);
|
||||
const frames = options && options.frames;
|
||||
if (!Number.isInteger(width) || width < 1 || width > 0x1000000 || !Number.isInteger(height) || height < 1 || height > 0x1000000) {
|
||||
throw new RangeError('Animated WebP dimensions are invalid');
|
||||
}
|
||||
if (!Array.isArray(frames) || frames.length < 2) throw new RangeError('Animated WebP requires at least two frames');
|
||||
const preparedFrames = frames.map((frame) => ({ frame, ...frameData(frame.webp) }));
|
||||
|
||||
const vp8x = Buffer.alloc(10);
|
||||
vp8x[0] = 0x02 | (preparedFrames.some(({ hasAlpha }) => hasAlpha) ? 0x10 : 0);
|
||||
writeUint24LE(vp8x, 4, width - 1);
|
||||
writeUint24LE(vp8x, 7, height - 1);
|
||||
const anim = Buffer.alloc(6);
|
||||
const background = options.background || [30, 30, 30, 255];
|
||||
anim.set([background[2] || 0, background[1] || 0, background[0] || 0, background[3] === undefined ? 255 : background[3]], 0);
|
||||
anim.writeUInt16LE(Math.max(0, Math.min(0xffff, options.plays || 0)), 4);
|
||||
|
||||
const chunks = [chunk('VP8X', vp8x), chunk('ANIM', anim)];
|
||||
for (const { frame, data } of preparedFrames) {
|
||||
const header = Buffer.alloc(16);
|
||||
writeUint24LE(header, 6, width - 1);
|
||||
writeUint24LE(header, 9, height - 1);
|
||||
writeUint24LE(header, 12, Math.max(1, Math.min(0xffffff, Math.round(frame.duration))));
|
||||
header[15] = 0x02;
|
||||
chunks.push(chunk('ANMF', Buffer.concat([header, data])));
|
||||
}
|
||||
const body = Buffer.concat([Buffer.from('WEBP'), ...chunks]);
|
||||
const output = Buffer.alloc(8 + body.length);
|
||||
output.write('RIFF', 0, 4, 'ascii');
|
||||
output.writeUInt32LE(body.length, 4);
|
||||
body.copy(output, 8);
|
||||
return output;
|
||||
}
|
||||
|
||||
module.exports = { encode };
|
||||
Reference in new issue
Block a user