mirror of
https://github.com/embarklabs/neo-blessed.git
synced 2025-01-09 10:42:02 +00:00
add PNG/ANSIImage element.
This commit is contained in:
parent
305aa063d4
commit
2842f3f6ce
75
README.md
75
README.md
@ -172,6 +172,7 @@ screen.render();
|
|||||||
- [Terminal](#terminal-from-box)
|
- [Terminal](#terminal-from-box)
|
||||||
- [Image](#image-from-box)
|
- [Image](#image-from-box)
|
||||||
- [Layout](#layout-from-element)
|
- [Layout](#layout-from-element)
|
||||||
|
- [PNG](#png-from-box)
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
@ -1384,11 +1385,13 @@ terminals.
|
|||||||
##### Methods:
|
##### Methods:
|
||||||
|
|
||||||
- inherits all from Box.
|
- inherits all from Box.
|
||||||
- __setImage(img, callback)__ - set the image in the box to a new path.
|
- __setImage(img, [callback])__ - set the image in the box to a new path.
|
||||||
- __clearImage(callback)__ - clear the current image.
|
- __clearImage([callback])__ - clear the current image.
|
||||||
- __imageSize(img, callback)__ - get the size of an image file in pixels.
|
- __imageSize(img, [callback])__ - get the size of an image file in pixels.
|
||||||
- __termSize(callback)__ - get the size of the terminal in pixels.
|
- __termSize([callback])__ - get the size of the terminal in pixels.
|
||||||
- __getPixelRatio(callback)__ - get the pixel to cell ratio for the terminal.
|
- __getPixelRatio([callback])__ - get the pixel to cell ratio for the terminal.
|
||||||
|
- _Note:_ All methods above can be synchronous as long as the host version of
|
||||||
|
node supports `spawnSync`.
|
||||||
|
|
||||||
|
|
||||||
#### Layout (from Element)
|
#### Layout (from Element)
|
||||||
@ -1565,6 +1568,68 @@ for (var i = 0; i < 10; i++) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### PNG (from Box)
|
||||||
|
|
||||||
|
Convert any `.png` file (or `.gif`, see below) to an ANSI image and display it
|
||||||
|
as an element. This differs from the `Image` element in that it uses blessed's
|
||||||
|
internal PNG parser and does not require external dependencies.
|
||||||
|
|
||||||
|
Blessed uses an internal from-scratch PNG reader because no other javascript
|
||||||
|
PNG reader supports Adam7 interlaced images (much less pass the png test
|
||||||
|
suite).
|
||||||
|
|
||||||
|
The blessed PNG reader supports adam7 deinterlacing, animation (APNG), all
|
||||||
|
color types, bit depths 1-32, alpha, alpha palettes, and outputs scaled bitmaps
|
||||||
|
(cellmaps) in blessed for efficient rendering to the screen buffer. It also
|
||||||
|
uses some code from libcaca/libcucul to add density ASCII characters in order
|
||||||
|
to give the image more detail in the terminal.
|
||||||
|
|
||||||
|
If a corrupt PNG or a non-PNG is passed in, blessed will display error text in
|
||||||
|
the element.
|
||||||
|
|
||||||
|
`.gif` files are also supported via a javascript implementation (they are
|
||||||
|
internally converted to bitmaps and fed to the PNG renderer). Any other image
|
||||||
|
format is support only if the user has imagemagick (`convert` and `identify`)
|
||||||
|
installed.
|
||||||
|
|
||||||
|
##### Options:
|
||||||
|
|
||||||
|
- inherits all from Box.
|
||||||
|
- __file__ - URL or path to PNG file. can also be a buffer.
|
||||||
|
- __scale__ - scale cellmap down (`0-1.0`) from its original pixel width/height
|
||||||
|
(default: `1.0`).
|
||||||
|
- __width/height__ - this differs from other element's `width` or `height` in
|
||||||
|
that only one of them is needed: blessed will maintain the aspect ratio of
|
||||||
|
the image as it scales down to the proper number of cells. __NOTE__: PNG's
|
||||||
|
are always automatically shrunken to size (based on scale) if a `width` or
|
||||||
|
`height` is not given.
|
||||||
|
- __ascii__ - add various "density" ASCII characters over the rendering to give
|
||||||
|
the image more detail, similar to libcaca/libcucul (the library mplayer uses
|
||||||
|
to display videos in the terminal).
|
||||||
|
- __animate__ - whether to animate if the image is an APNG. if false, only
|
||||||
|
display the first frame or IDAT (default: `true`).
|
||||||
|
|
||||||
|
##### Properties:
|
||||||
|
|
||||||
|
- inherits all from Box.
|
||||||
|
- __img__ - image object from the png reader.
|
||||||
|
- __img.width__ - pixel width.
|
||||||
|
- __img.height__ - pixel height.
|
||||||
|
- __img.bmp__ - image bitmap.
|
||||||
|
- __img.cellmap__ - image cellmap (bitmap scaled down to cell size).
|
||||||
|
|
||||||
|
##### Events:
|
||||||
|
|
||||||
|
- inherits all from Box.
|
||||||
|
|
||||||
|
##### Methods:
|
||||||
|
|
||||||
|
- inherits all from Box.
|
||||||
|
- __setImage(file)__ - set the image in the box to a new path. file can be a
|
||||||
|
path, url, or buffer.
|
||||||
|
- __clearImage()__ - clear the image.
|
||||||
|
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ widget.classes = [
|
|||||||
'ListTable',
|
'ListTable',
|
||||||
'Terminal',
|
'Terminal',
|
||||||
'Image',
|
'Image',
|
||||||
'Layout'
|
'Layout',
|
||||||
|
'PNG'
|
||||||
];
|
];
|
||||||
|
|
||||||
widget.classes.forEach(function(name) {
|
widget.classes.forEach(function(name) {
|
||||||
|
142
lib/widgets/png.js
Normal file
142
lib/widgets/png.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* png.js - render PNGs as ANSI
|
||||||
|
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
|
||||||
|
* https://github.com/chjj/blessed
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules
|
||||||
|
*/
|
||||||
|
|
||||||
|
var cp = require('child_process')
|
||||||
|
, path = require('path')
|
||||||
|
, fs = require('fs');
|
||||||
|
|
||||||
|
var helpers = require('../helpers');
|
||||||
|
var colors = require('../colors');
|
||||||
|
|
||||||
|
var Node = require('./node');
|
||||||
|
var Box = require('./box');
|
||||||
|
|
||||||
|
var tng = require('../../vendor/tng');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PNG
|
||||||
|
*/
|
||||||
|
|
||||||
|
function PNG(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!(this instanceof Node)) {
|
||||||
|
return new PNG(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
options.shrink = true;
|
||||||
|
|
||||||
|
Box.call(this, options);
|
||||||
|
|
||||||
|
this.scale = this.options.scale || 1.0;
|
||||||
|
this.options.animate = this.options.animate !== false;
|
||||||
|
this._noFill = true;
|
||||||
|
|
||||||
|
if (this.options.file) {
|
||||||
|
this.setImage(this.options.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.screen.on('prerender', function() {
|
||||||
|
var lpos = self.lpos;
|
||||||
|
if (!lpos) return;
|
||||||
|
// prevent image from blending with itself if there are alpha channels
|
||||||
|
self.screen.clearRegion(lpos.xi, lpos.xl, lpos.yi, lpos.yl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
PNG.prototype.__proto__ = Box.prototype;
|
||||||
|
|
||||||
|
PNG.prototype.type = 'png';
|
||||||
|
|
||||||
|
PNG.curl = function(url) {
|
||||||
|
try {
|
||||||
|
return cp.execFileSync('curl',
|
||||||
|
['-s', '-A', '', url],
|
||||||
|
{ stdio: ['ignore', 'pipe', 'ignore'] });
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return cp.execFileSync('wget',
|
||||||
|
['-U', '', '-O', '-', url],
|
||||||
|
{ stdio: ['ignore', 'pipe', 'ignore'] });
|
||||||
|
} catch (e) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
throw new Error('curl or wget failed.');
|
||||||
|
};
|
||||||
|
|
||||||
|
PNG.prototype.setImage = function(file) {
|
||||||
|
var self = this;
|
||||||
|
if (/^https?:/.test(file)) {
|
||||||
|
file = PNG.curl(file);
|
||||||
|
}
|
||||||
|
this.file = file;
|
||||||
|
var width = this.position.width;
|
||||||
|
var height = this.position.height;
|
||||||
|
if (width != null) {
|
||||||
|
width = this.width;
|
||||||
|
}
|
||||||
|
if (height != null) {
|
||||||
|
height = this.height;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.setContent('');
|
||||||
|
this.img = tng(this.file, {
|
||||||
|
colors: colors,
|
||||||
|
cellmapWidth: width,
|
||||||
|
cellmapHeight: height,
|
||||||
|
cellmapScale: this.scale,
|
||||||
|
ascii: this.options.ascii
|
||||||
|
});
|
||||||
|
if (width == null || height == null) {
|
||||||
|
this.width = this.img.cellmap[0].length;
|
||||||
|
this.height = this.img.cellmap.length;
|
||||||
|
}
|
||||||
|
if (this.img.frames && this.options.animate) {
|
||||||
|
this.img.play(function(bmp, cellmap) {
|
||||||
|
self.cellmap = cellmap;
|
||||||
|
self.screen.render();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.cellmap = self.img.cellmap;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.setContent('PNG Error: ' + e.message);
|
||||||
|
this.img = null;
|
||||||
|
this.cellmap = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PNG.prototype.clearImage = function() {
|
||||||
|
this.setContent('');
|
||||||
|
this.img = null;
|
||||||
|
this.cellmap = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
PNG.prototype.render = function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var coords = this._render();
|
||||||
|
if (!coords) return;
|
||||||
|
|
||||||
|
if (this.img) {
|
||||||
|
this.img.renderElement(this.cellmap, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coords;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = PNG;
|
69
test/widget-png.js
Normal file
69
test/widget-png.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
var blessed = require('../');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var screen = blessed.screen({
|
||||||
|
tput: true,
|
||||||
|
smartCSR: true,
|
||||||
|
dump: __dirname + '/logs/png.log'
|
||||||
|
});
|
||||||
|
|
||||||
|
var box = blessed.box({
|
||||||
|
parent: screen,
|
||||||
|
left: 4,
|
||||||
|
top: 3,
|
||||||
|
width: 10,
|
||||||
|
height: 6,
|
||||||
|
border: 'line',
|
||||||
|
style: {
|
||||||
|
bg: 'green'
|
||||||
|
},
|
||||||
|
content: 'Lorem ipsum doler',
|
||||||
|
align: 'center'
|
||||||
|
});
|
||||||
|
|
||||||
|
var file = process.argv[2];
|
||||||
|
var testImage = __dirname + '/test-image.png';
|
||||||
|
var spinfox = __dirname + '/spinfox.png';
|
||||||
|
|
||||||
|
// XXX I'm not sure of the license of this file,
|
||||||
|
// so I'm not going to redistribute it in the repo.
|
||||||
|
var url = 'https://people.mozilla.org/~dolske/apng/spinfox.png';
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(spinfox)) {
|
||||||
|
var buf = blessed.png.curl(url);
|
||||||
|
fs.writeFileSync(spinfox, buf);
|
||||||
|
}
|
||||||
|
file = spinfox;
|
||||||
|
} catch (e) {
|
||||||
|
file = testImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var png = blessed.png({
|
||||||
|
parent: screen,
|
||||||
|
// border: 'line',
|
||||||
|
width: 20,
|
||||||
|
height: 19,
|
||||||
|
top: 2,
|
||||||
|
left: 0,
|
||||||
|
file: file,
|
||||||
|
ascii: false,
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.render();
|
||||||
|
|
||||||
|
screen.key('q', function() {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
var timeout = setInterval(function() {
|
||||||
|
png.left++;
|
||||||
|
screen.render();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
png.on('mousedown', function() {
|
||||||
|
clearInterval(timeout);
|
||||||
|
});
|
3
vendor/tng/.gitignore
vendored
Normal file
3
vendor/tng/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
debug.log
|
||||||
|
png.json
|
6
vendor/tng/.npmignore
vendored
Normal file
6
vendor/tng/.npmignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.git*
|
||||||
|
test/
|
||||||
|
img/
|
||||||
|
node_modules/
|
||||||
|
debug.log
|
||||||
|
png.json
|
20
vendor/tng/LICENSE
vendored
Normal file
20
vendor/tng/LICENSE
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2015, Christopher Jeffrey
|
||||||
|
https://github.com/chjj/tng
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
39
vendor/tng/README.md
vendored
Normal file
39
vendor/tng/README.md
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# tng
|
||||||
|
|
||||||
|
A full-featured PNG renderer for the terminal, built for [blessed][blessed].
|
||||||
|
|
||||||
|
![tng](https://raw.githubusercontent.com/chjj/blessed/master/img/demo.png)
|
||||||
|
|
||||||
|
Convert any `.png` file (or `.gif`, see below) to an ANSI image and display it
|
||||||
|
as an element or ANSI text.
|
||||||
|
|
||||||
|
Blessed uses an internal from-scratch PNG reader because no other javascript
|
||||||
|
PNG reader supports Adam7 interlaced images (much less pass the png test
|
||||||
|
suite).
|
||||||
|
|
||||||
|
The blessed PNG reader supports adam7 deinterlacing, animation (APNG), all
|
||||||
|
color types, bit depths 1-32, alpha, alpha palettes, and outputs scaled bitmaps
|
||||||
|
(cellmaps) in blessed for efficient rendering to the screen buffer. It also
|
||||||
|
uses some code from libcaca/libcucul to add density ASCII characters in order
|
||||||
|
to give the image more detail in the terminal.
|
||||||
|
|
||||||
|
`.gif` files are also supported via a javascript implementation (they are
|
||||||
|
internally converted to bitmaps and fed to the PNG renderer). Any other image
|
||||||
|
format is support only if the user has imagemagick (`convert` and `identify`)
|
||||||
|
installed.
|
||||||
|
|
||||||
|
|
||||||
|
## Contribution and License Agreement
|
||||||
|
|
||||||
|
If you contribute code to this project, you are implicitly allowing your code
|
||||||
|
to be distributed under the MIT license. You are also implicitly verifying that
|
||||||
|
all code is your original work. `</legalese>`
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2015, Christopher Jeffrey. (MIT License)
|
||||||
|
|
||||||
|
See LICENSE for more info.
|
||||||
|
|
||||||
|
[blessed]: https://github.com/chjj/blessed
|
1
vendor/tng/index.js
vendored
Normal file
1
vendor/tng/index.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./lib/tng.js');
|
399
vendor/tng/lib/gif.js
vendored
Normal file
399
vendor/tng/lib/gif.js
vendored
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
/**
|
||||||
|
* gif.js - gif reader for tng
|
||||||
|
* Copyright (c) 2015, Christopher Jeffrey (MIT License).
|
||||||
|
* https://github.com/chjj/tng
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fs = require('fs')
|
||||||
|
, cp = require('child_process')
|
||||||
|
, path = require('path')
|
||||||
|
, assert = require('assert');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GIF
|
||||||
|
*/
|
||||||
|
|
||||||
|
function GIF(file, options) {
|
||||||
|
var info = {}
|
||||||
|
, p = 0
|
||||||
|
, buf
|
||||||
|
, i
|
||||||
|
, total
|
||||||
|
, sig
|
||||||
|
, desc
|
||||||
|
, img
|
||||||
|
, ext
|
||||||
|
, label
|
||||||
|
, size;
|
||||||
|
|
||||||
|
if (!file) throw new Error('no file');
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (Buffer.isBuffer(file)) {
|
||||||
|
buf = file;
|
||||||
|
file = null;
|
||||||
|
} else {
|
||||||
|
file = path.resolve(process.cwd(), file);
|
||||||
|
buf = fs.readFileSync(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = buf.slice(0, 6).toString('ascii');
|
||||||
|
if (sig !== 'GIF87a' && sig !== 'GIF89a') {
|
||||||
|
throw new Error('bad header: ' + sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.screenWidth = buf.readUInt16LE(6);
|
||||||
|
info.screenHeight = buf.readUInt16LE(8);
|
||||||
|
|
||||||
|
info.flags = buf.readUInt8(10);
|
||||||
|
info.gct = !!(info.flags & 0x80);
|
||||||
|
info.gctsize = (info.flags & 0x07) + 1;
|
||||||
|
|
||||||
|
info.bgIndex = buf.readUInt8(11);
|
||||||
|
info.aspect = buf.readUInt8(12);
|
||||||
|
p += 13;
|
||||||
|
|
||||||
|
if (info.gct) {
|
||||||
|
info.colors = [];
|
||||||
|
total = 1 << info.gctsize;
|
||||||
|
for (i = 0; i < total; i++, p += 3) {
|
||||||
|
info.colors.push([buf[p], buf[p + 1], buf[p + 2], 255]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.images = [];
|
||||||
|
info.extensions = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (p < buf.length) {
|
||||||
|
desc = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
if (desc === 0x2c) {
|
||||||
|
img = {};
|
||||||
|
|
||||||
|
img.left = buf.readUInt16LE(p);
|
||||||
|
p += 2;
|
||||||
|
img.top = buf.readUInt16LE(p);
|
||||||
|
p += 2;
|
||||||
|
|
||||||
|
img.width = buf.readUInt16LE(p);
|
||||||
|
p += 2;
|
||||||
|
img.height = buf.readUInt16LE(p);
|
||||||
|
p += 2;
|
||||||
|
|
||||||
|
img.flags = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
|
||||||
|
img.lct = !!(img.flags & 0x80);
|
||||||
|
img.ilace = !!(img.flags & 0x40);
|
||||||
|
img.lctsize = (img.flags & 0x07) + 1;
|
||||||
|
|
||||||
|
if (img.lct) {
|
||||||
|
img.lcolors = [];
|
||||||
|
total = 1 << img.lctsize;
|
||||||
|
for (i = 0; i < total; i++, p += 3) {
|
||||||
|
img.lcolors.push([buf[p], buf[p + 1], buf[p + 2], 255]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img.codeSize = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
|
||||||
|
img.size = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
|
||||||
|
img.lzw = [buf.slice(p, p + img.size)];
|
||||||
|
p += img.size;
|
||||||
|
|
||||||
|
while (buf[p] !== 0x00) {
|
||||||
|
// Some gifs screw up their size.
|
||||||
|
// XXX Same for all subblocks?
|
||||||
|
if (buf[p] === 0x3b) {
|
||||||
|
p--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
img.lzw.push(buf.slice(p, p + size));
|
||||||
|
p += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(buf.readUInt8(p), 0x00);
|
||||||
|
p += 1;
|
||||||
|
|
||||||
|
info.images.push(img);
|
||||||
|
} else if (desc === 0x21) {
|
||||||
|
// Extensions:
|
||||||
|
// http://www.w3.org/Graphics/GIF/spec-gif89a.txt
|
||||||
|
ext = {};
|
||||||
|
label = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
ext.label = label;
|
||||||
|
if (label === 0xf9) {
|
||||||
|
size = buf.readUInt8(p);
|
||||||
|
assert.equal(size, 0x04);
|
||||||
|
p += 1;
|
||||||
|
ext.fields = buf.readUInt8(p);
|
||||||
|
ext.disposeMethod = (ext.fields >> 2) & 0x07;
|
||||||
|
ext.useTransparent = !!(ext.fields & 0x01);
|
||||||
|
p += 1;
|
||||||
|
ext.delay = buf.readUInt16LE(p);
|
||||||
|
p += 2;
|
||||||
|
ext.transparentColor = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
while (buf[p] !== 0x00) {
|
||||||
|
size = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
p += size;
|
||||||
|
}
|
||||||
|
assert.equal(buf.readUInt8(p), 0x00);
|
||||||
|
p += 1;
|
||||||
|
info.delay = ext.delay;
|
||||||
|
info.transparentColor = ext.transparentColor;
|
||||||
|
info.disposeMethod = ext.disposeMethod;
|
||||||
|
info.useTransparent = ext.useTransparent;
|
||||||
|
} else if (label === 0xff) {
|
||||||
|
size = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
ext.id = buf.slice(p, p + 8).toString('ascii');
|
||||||
|
p += 8;
|
||||||
|
ext.auth = buf.slice(p, p + 3).toString('ascii');
|
||||||
|
p += 3;
|
||||||
|
ext.data = [];
|
||||||
|
while (buf[p] !== 0x00) {
|
||||||
|
size = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
ext.data.push(buf.slice(p, p + size));
|
||||||
|
p += size;
|
||||||
|
}
|
||||||
|
// http://graphcomp.com/info/specs/ani_gif.html
|
||||||
|
if (ext.id === 'NETSCAPE' && ext.auth === '2.0') {
|
||||||
|
assert.equal(ext.data[0].readUInt8(0), 0x01);
|
||||||
|
ext.numPlays = ext.data[0].readUInt16LE(1);
|
||||||
|
info.numPlays = ext.numPlays;
|
||||||
|
}
|
||||||
|
assert.equal(buf.readUInt8(p), 0x00);
|
||||||
|
p += 1;
|
||||||
|
} else {
|
||||||
|
ext.data = [];
|
||||||
|
while (buf[p] !== 0x00) {
|
||||||
|
size = buf.readUInt8(p);
|
||||||
|
p += 1;
|
||||||
|
ext.data.push(buf.slice(p, p + size));
|
||||||
|
p += size;
|
||||||
|
}
|
||||||
|
assert.equal(buf.readUInt8(p), 0x00);
|
||||||
|
p += 1;
|
||||||
|
}
|
||||||
|
info.extensions.push(ext);
|
||||||
|
} else if (desc === 0x3b) {
|
||||||
|
break;
|
||||||
|
} else if (p === buf.length - 1) {
|
||||||
|
// } else if (desc === 0x00 && p === buf.length - 1) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
throw new Error('unknown block');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (options.debug) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.images = info.images.map(function(img) {
|
||||||
|
img.lzw = new Buffer(img.lzw.reduce(function(out, data) {
|
||||||
|
return out.concat(Array.prototype.slice.call(data));
|
||||||
|
}, []));
|
||||||
|
|
||||||
|
try {
|
||||||
|
img.data = decompress(img.lzw, img.codeSize);
|
||||||
|
} catch (e) {
|
||||||
|
if (options.debug) throw e;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var interlacing = [
|
||||||
|
[ 0, 8 ],
|
||||||
|
[ 4, 8 ],
|
||||||
|
[ 2, 4 ],
|
||||||
|
[ 1, 2 ],
|
||||||
|
[ 0, 0 ]
|
||||||
|
];
|
||||||
|
|
||||||
|
var table = img.lcolors || info.colors
|
||||||
|
, row = 0
|
||||||
|
, col = 0
|
||||||
|
, ilp = 0
|
||||||
|
, p = 0
|
||||||
|
, b
|
||||||
|
, idx
|
||||||
|
, i
|
||||||
|
, y
|
||||||
|
, x
|
||||||
|
, line
|
||||||
|
, pixel;
|
||||||
|
|
||||||
|
img.samples = [];
|
||||||
|
// Rewritten version of:
|
||||||
|
// https://github.com/lbv/ka-cs-programs/blob/master/lib/gif-reader.js
|
||||||
|
for (;;) {
|
||||||
|
b = img.data[p++];
|
||||||
|
if (b == null) break;
|
||||||
|
idx = (row * img.width + col) * 4;
|
||||||
|
if (!table[b]) {
|
||||||
|
if (options.debug) throw new Error('bad samples');
|
||||||
|
table[b] = [0, 0, 0, 0];
|
||||||
|
}
|
||||||
|
img.samples[idx] = table[b][0];
|
||||||
|
img.samples[idx + 1] = table[b][1];
|
||||||
|
img.samples[idx + 2] = table[b][2];
|
||||||
|
img.samples[idx + 3] = table[b][3];
|
||||||
|
if (info.useTransparent && b === info.transparentColor) {
|
||||||
|
img.samples[idx + 3] = 0;
|
||||||
|
}
|
||||||
|
if (++col >= img.width) {
|
||||||
|
col = 0;
|
||||||
|
if (img.ilace) {
|
||||||
|
row += interlacing[ilp][1];
|
||||||
|
if (row >= img.height) {
|
||||||
|
row = interlacing[++ilp][0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img.pixels = [];
|
||||||
|
for (i = 0; i < img.samples.length; i += 4) {
|
||||||
|
img.pixels.push(img.samples.slice(i, i + 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
img.bmp = [];
|
||||||
|
for (y = 0, p = 0; y < img.height; y++) {
|
||||||
|
line = [];
|
||||||
|
for (x = 0; x < img.width; x++) {
|
||||||
|
pixel = img.pixels[p++];
|
||||||
|
if (!pixel) {
|
||||||
|
if (options.debug) throw new Error('no pixel');
|
||||||
|
line.push({ r: 0, g: 0, b: 0, a: 0 });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
line.push({ r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3] });
|
||||||
|
}
|
||||||
|
img.bmp.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}, this).filter(Boolean);
|
||||||
|
|
||||||
|
if (!info.images.length) {
|
||||||
|
throw new Error('no image data or bad decompress');
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewritten version of:
|
||||||
|
// https://github.com/lbv/ka-cs-programs/blob/master/lib/gif-reader.js
|
||||||
|
function decompress(input, codeSize) {
|
||||||
|
var bitDepth = codeSize + 1
|
||||||
|
, CC = 1 << codeSize
|
||||||
|
, EOI = CC + 1
|
||||||
|
, stack = []
|
||||||
|
, table = []
|
||||||
|
, ntable = 0
|
||||||
|
, oldCode = null
|
||||||
|
, buffer = 0
|
||||||
|
, nbuffer = 0
|
||||||
|
, p = 0
|
||||||
|
, buf = []
|
||||||
|
, bits
|
||||||
|
, read
|
||||||
|
, ans
|
||||||
|
, n
|
||||||
|
, code
|
||||||
|
, i
|
||||||
|
, K
|
||||||
|
, b
|
||||||
|
, maxElem;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (stack.length === 0) {
|
||||||
|
bits = bitDepth;
|
||||||
|
read = 0;
|
||||||
|
ans = 0;
|
||||||
|
while (read < bits) {
|
||||||
|
if (nbuffer === 0) {
|
||||||
|
if (p >= input.length) return buf;
|
||||||
|
buffer = input[p++];
|
||||||
|
nbuffer = 8;
|
||||||
|
}
|
||||||
|
n = Math.min(bits - read, nbuffer);
|
||||||
|
ans |= (buffer & ((1 << n) - 1)) << read;
|
||||||
|
read += n;
|
||||||
|
nbuffer -= n;
|
||||||
|
buffer >>= n;
|
||||||
|
}
|
||||||
|
code = ans;
|
||||||
|
|
||||||
|
if (code === EOI) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code === CC) {
|
||||||
|
table = [];
|
||||||
|
for (i = 0; i < CC; ++i) {
|
||||||
|
table[i] = [i, -1, i];
|
||||||
|
}
|
||||||
|
bitDepth = codeSize + 1;
|
||||||
|
maxElem = 1 << bitDepth;
|
||||||
|
ntable = CC + 2;
|
||||||
|
oldCode = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldCode === null) {
|
||||||
|
oldCode = code;
|
||||||
|
buf.push(table[code][0]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code < ntable) {
|
||||||
|
for (i = code; i >= 0; i = table[i][1]) {
|
||||||
|
stack.push(table[i][0]);
|
||||||
|
}
|
||||||
|
table[ntable++] = [
|
||||||
|
table[code][2],
|
||||||
|
oldCode,
|
||||||
|
table[oldCode][2]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
K = table[oldCode][2];
|
||||||
|
table[ntable++] = [K, oldCode, K];
|
||||||
|
for (i = code; i >= 0; i = table[i][1]) {
|
||||||
|
stack.push(table[i][0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCode = code;
|
||||||
|
if (ntable === maxElem) {
|
||||||
|
maxElem = 1 << (++bitDepth);
|
||||||
|
if (bitDepth > 12) bitDepth = 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b = stack.pop();
|
||||||
|
if (b == null) break;
|
||||||
|
buf.push(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = GIF;
|
1217
vendor/tng/lib/tng.js
vendored
Normal file
1217
vendor/tng/lib/tng.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
17
vendor/tng/package.json
vendored
Normal file
17
vendor/tng/package.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "tng",
|
||||||
|
"description": "A full-featured PNG renderer for the terminal.",
|
||||||
|
"author": "Christopher Jeffrey",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "./lib/tng.js",
|
||||||
|
"bin": "./test/index.js",
|
||||||
|
"preferGlobal": false,
|
||||||
|
"repository": "git://github.com/chjj/tng.git",
|
||||||
|
"homepage": "https://github.com/chjj/tng",
|
||||||
|
"bugs": { "url": "http://github.com/chjj/tng/issues" },
|
||||||
|
"keywords": ["png", "gif", "image", "terminal", "term", "tty", "tui"],
|
||||||
|
"tags": ["png", "gif", "image", "terminal", "term", "tty", "tui"],
|
||||||
|
"peerDependencies": {
|
||||||
|
"blessed": ">=0.1.61"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user