Last active
May 17, 2021 17:07
-
-
Save lahmatiy/1460bd0a1b8812072cfea1bb55d94142 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const EXTENSION_TYPE = { | |
0x01: 'PlainText', | |
0xF9: 'GraphicControl', | |
0xFE: 'Comment', | |
0xFF: 'Application' | |
}; | |
/** | |
* Returns total length of data blocks sequence | |
* | |
* @param {Buffer} buffer | |
* @param {number} offset | |
* @returns {number} | |
*/ | |
function getBlockLength(buffer, offset) { | |
let length = 0; | |
while (buffer[offset + length] !== 0) { | |
length += buffer[offset + length] + 1; | |
} | |
return length + 1; | |
} | |
/** | |
* Checks if buffer contains a GIF image | |
* | |
* @param {Buffer} buffer | |
* @returns {boolean} | |
*/ | |
function isGIF(buffer) { | |
const header = buffer.slice(0, 6).toString('ascii'); | |
return header === 'GIF87a' || header === 'GIF89a'; | |
} | |
/** | |
* Convert an animated GIF into a static | |
* | |
* @param {Buffer} input A GIF image | |
* @returns {Buffer} Converted GIF | |
*/ | |
module.exports = function makeGifStatic(input) { | |
/* eslint-disable require-jsdoc, no-use-before-define */ | |
function write(bytes) { | |
input.copy(output, outputOffset, offset, offset + bytes); | |
offset += bytes; | |
outputOffset += bytes; | |
} | |
// Check if this is this image has valid GIF header. | |
// If not return false. Chrome, FF and IE doesn't handle GIFs with invalid version. | |
if (!isGIF(input)) { | |
return input.slice(); | |
} | |
const output = Buffer.alloc(input.length); | |
let outputOffset = 0; | |
let offset = 0; | |
let hasColorTable; | |
let colorTableSize; | |
let imagesCount = 0; | |
// header, logical screen descriptor and global color table | |
write(6 + 7); // 6 bytes header + 7 bytes screen | |
hasColorTable = input[6 + 4] & 0x80; // 0b10000000 | |
colorTableSize = input[6 + 4] & 0x07; // 0b00000111 | |
if (hasColorTable) { | |
write(3 * Math.pow(2, colorTableSize + 1)); | |
} | |
while (offset < input.length) { | |
switch (input[offset]) { | |
// Extension block introducer | |
case 0x21: { | |
const label = input[offset + 1]; | |
const type = EXTENSION_TYPE[label] || 'Unknown'; | |
const dataLength = 2 + getBlockLength(input, offset + 2); | |
if (type === 'GraphicControl' || type === 'Application') { | |
write(dataLength); | |
} else { | |
offset += dataLength; | |
} | |
break; | |
} | |
// Image descriptor block. According to specification there could be any | |
// number of these blocks (even zero). When there is more than one image | |
// descriptor browsers will display animation (they shouldn't when there | |
// is no delays defined, but they do it anyway). | |
case 0x2C: { | |
let dataLength = 11; | |
hasColorTable = input[offset + 9] & 0x80; // 0b10000000 | |
colorTableSize = input[offset + 9] & 0x07; // 0b00000111 | |
if (hasColorTable) { | |
dataLength += 3 * Math.pow(2, colorTableSize + 1); | |
} | |
dataLength += getBlockLength(input, offset + dataLength); | |
if (imagesCount === 0) { | |
write(dataLength); | |
} else { | |
offset += dataLength; | |
} | |
imagesCount++; | |
break; | |
} | |
// Stop processing on trailer block, | |
// all data after this point will is ignored by decoders | |
case 0x3B: | |
output[outputOffset++] = 0x3B; | |
offset = input.length; | |
break; | |
// Oops! This GIF seems to be invalid | |
default: | |
offset = input.length; // fast forward to end of input | |
break; | |
} | |
} | |
return output.slice(0, outputOffset); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var fs = require('fs'); | |
var animated = fs.readFileSync('./test.gif'); | |
var static = makeGifStatic(animated); | |
fs.writeFileSync('./test-static.gif', static); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
3 * Math.pow(2, colorTableSize + 1)
is equal to3 * 2 << colorTableSize
and6 << colorTableSize
for non-negative integercolorTableSize