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)
|
||||
- [Image](#image-from-box)
|
||||
- [Layout](#layout-from-element)
|
||||
- [PNG](#png-from-box)
|
||||
|
||||
### Other
|
||||
|
||||
|
@ -1384,11 +1385,13 @@ terminals.
|
|||
##### Methods:
|
||||
|
||||
- inherits all from Box.
|
||||
- __setImage(img, callback)__ - set the image in the box to a new path.
|
||||
- __clearImage(callback)__ - clear the current image.
|
||||
- __imageSize(img, callback)__ - get the size of an image file in pixels.
|
||||
- __termSize(callback)__ - get the size of the terminal in pixels.
|
||||
- __getPixelRatio(callback)__ - get the pixel to cell ratio for the terminal.
|
||||
- __setImage(img, [callback])__ - set the image in the box to a new path.
|
||||
- __clearImage([callback])__ - clear the current image.
|
||||
- __imageSize(img, [callback])__ - get the size of an image file in pixels.
|
||||
- __termSize([callback])__ - get the size of the terminal in pixels.
|
||||
- __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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -36,7 +36,8 @@ widget.classes = [
|
|||
'ListTable',
|
||||
'Terminal',
|
||||
'Image',
|
||||
'Layout'
|
||||
'Layout',
|
||||
'PNG'
|
||||
];
|
||||
|
||||
widget.classes.forEach(function(name) {
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
debug.log
|
||||
png.json
|
|
@ -0,0 +1,6 @@
|
|||
.git*
|
||||
test/
|
||||
img/
|
||||
node_modules/
|
||||
debug.log
|
||||
png.json
|
|
@ -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.
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('./lib/tng.js');
|
|
@ -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;
|
File diff suppressed because it is too large
Load Diff
|
@ -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…
Reference in New Issue