Skip to content

Commit

Permalink
feat: add APNG support
Browse files Browse the repository at this point in the history
  • Loading branch information
qzb committed Mar 25, 2019
1 parent 232c2d0 commit 16eef6b
Show file tree
Hide file tree
Showing 17 changed files with 136 additions and 38 deletions.
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,7 @@
[![standard][standard-image]][standard-url]
[![coverage][coveralls-image]][coveralls-url]

[npm-image]: https://img.shields.io/npm/v/is-animated.svg
[npm-url]: https://www.npmjs.com/package/is-animated
[travis-image]: https://img.shields.io/travis/qzb/is-animated.svg
[travis-url]: https://travis-ci.org/qzb/is-animated
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
[standard-url]: http://npm.im/standard
[coveralls-image]: https://img.shields.io/coveralls/qzb/is-animated/master.svg
[coveralls-url]: https://coveralls.io/r/qzb/is-animated?branch=master

**is-animated** is a simple library for checking if specified GIF is animated.
It doesn't care if gif is valid or not, invalid GIFs are treated as not-animated ones.
**is-animated** is a simple library for detecting animated images, it supports not only GIFs, but also APNG images.

## Install

Expand All @@ -41,3 +31,15 @@ fs.readFile(filename, (err, buffer) => {
## License

[MIT](LICENSE.md)


[npm-image]: https://img.shields.io/npm/v/is-animated.svg
[npm-url]: https://www.npmjs.com/package/is-animated
[travis-image]: https://img.shields.io/travis/qzb/is-animated.svg
[travis-url]: https://travis-ci.org/qzb/is-animated
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
[standard-url]: http://npm.im/standard
[coveralls-image]: https://img.shields.io/coveralls/qzb/is-animated/master.svg
[coveralls-url]: https://coveralls.io/r/qzb/is-animated?branch=master


24 changes: 24 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict'

var gif = require('./types/gif')
var png = require('./types/png')

/**
* Checks if buffer contains animated image
*
* @param {Buffer} buffer
* @returns {boolean}
*/
function isAnimated (buffer) {
if (gif.isGIF(buffer)) {
return gif.isAnimated(buffer)
}

if (png.isPNG(buffer)) {
return png.isAnimated(buffer)
}

return false
}

module.exports = isAnimated
15 changes: 12 additions & 3 deletions index.js → lib/types/gif.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,24 @@ function getDataBlocksLength (buffer, offset) {
return length + 1
}

/**
* Checks if buffer contains GIF image
*
* @param {Buffer} buffer
* @returns {boolean}
*/
exports.isGIF = function (buffer) {
var header = buffer.slice(0, 3).toString('ascii')
return (header === 'GIF')
}

/**
* Checks if buffer contains animated GIF image
*
* @param {Buffer} buffer
* @returns {boolean}
*/
function isAnimated (buffer) {
exports.isAnimated = function (buffer) {
var hasColorTable, colorTableSize, header
var offset = 0
var imagesCount = 0
Expand Down Expand Up @@ -88,5 +99,3 @@ function isAnimated (buffer) {

return (imagesCount > 1)
}

module.exports = isAnimated
52 changes: 52 additions & 0 deletions lib/types/png.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
exports.isPNG = function (buffer) {
var header = buffer.slice(0, 8).toString('hex')
return (header === '89504e470d0a1a0a') // \211 P N G \r \n \032 'n
}

exports.isAnimated = function (buffer) {
var hasACTL = false
var hasIDAT = false
var hasFDAT = false

var previousChunkType = null

var offset = 8

while (offset < buffer.length) {
var chunkLength = buffer.readUInt32BE(offset)
var chunkType = buffer.slice(offset + 4, offset + 8).toString('ascii')

switch (chunkType) {
case 'acTL':
hasACTL = true
break
case 'IDAT':
if (!hasACTL) {
return false
}

if (previousChunkType !== 'fcTL') {
return false
}

hasIDAT = true
break
case 'fdAT':
if (!hasIDAT) {
return false
}

if (previousChunkType !== 'fcTL') {
return false
}

hasFDAT = true
break
}

previousChunkType = chunkType
offset += 4 + 4 + chunkLength + 4
}

return (hasACTL && hasIDAT && hasFDAT)
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "is-animated",
"description": "Checks if GIF image is animated",
"version": "1.0.0",
"description": "Detects animated GIF and APNG images",
"version": "1.1.0",
"author": "Józef Sokołowski <j.k.sokolowski@gmail.com>",
"bugs": {
"url": "https://github.com/qzb/is-animated/issues"
Expand All @@ -20,7 +20,7 @@
"image"
],
"license": "MIT",
"main": "index.js",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/qzb/is-animated.git"
Expand Down
Binary file added test/animated/regular.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 32 additions & 21 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
'use strict'

var fs = require('fs')
var path = require('path')
var test = require('tape')
var isAnimated = require('../index')
var isAnimated = require('../lib')

test('Test animated GIFs', function (t) {
var images = fs.readdirSync('./test/animated')
var types = [ 'gif', 'png' ]

t.plan(images.length)
types.forEach(function (type) {
test('Test animated ' + type.toUpperCase() + ' images', function (t) {
var images = fs.readdirSync('./test/animated').filter(function (name) {
return path.extname(name).slice(1) === type
})

images.forEach(function (imgName) {
var buffer = fs.readFileSync('./test/animated/' + imgName)
t.true(isAnimated(buffer), imgName)
t.plan(images.length)

images.forEach(function (imgName) {
var buffer = fs.readFileSync('./test/animated/' + imgName)
t.true(isAnimated(buffer), imgName)
})
})
})

test('Test static GIFs', function (t) {
var images = fs.readdirSync('./test/static')
test('Test static ' + type.toUpperCase() + ' images', function (t) {
var images = fs.readdirSync('./test/static').filter(function (name) {
return path.extname(name).slice(1) === type
})

t.plan(images.length)
t.plan(images.length)

images.forEach(function (imgName) {
var buffer = fs.readFileSync('./test/static/' + imgName)
t.false(isAnimated(buffer), imgName)
images.forEach(function (imgName) {
var buffer = fs.readFileSync('./test/static/' + imgName)
t.false(isAnimated(buffer), imgName)
})
})
})

test('Test invalid GIFs', function (t) {
var images = fs.readdirSync('./test/invalid')
test('Test invalid ' + type.toUpperCase() + ' images', function (t) {
var images = fs.readdirSync('./test/invalid').filter(function (name) {
return path.extname(name).slice(1) === type
})

t.plan(images.length)
t.plan(images.length)

images.forEach(function (imgName) {
var buffer = fs.readFileSync('./test/invalid/' + imgName)
t.false(isAnimated(buffer), imgName)
images.forEach(function (imgName) {
var buffer = fs.readFileSync('./test/invalid/' + imgName)
t.false(isAnimated(buffer), imgName)
})
})
})
Binary file added test/invalid/no-IDAT-chunk.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed test/invalid/png.gif
Binary file not shown.
Binary file added test/static/actl-chunk-after-fdat-chunks.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/idat-chunk-after-fdat-chunks.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/no-acTL-chunk.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/no-fcTL-chunk-before-IDAT-chunk.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/no-fcTL-chunk-before-last-fdAT-chunk.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/no-fcTL-chunks.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/no-fdAT-chunks.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/static/regular.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 16eef6b

Please sign in to comment.