diff --git a/README.md b/README.md index 4ad62cd..19d05a2 100644 --- a/README.md +++ b/README.md @@ -94,11 +94,12 @@ var box = blessed.box({ // Append our box to the screen. screen.append(box); -// Add a PNG icon to the box (X11 only) +// Add a png icon to the box var icon = blessed.image({ parent: box, top: 0, left: 0, + itype: 'overlay', width: 'shrink', height: 'shrink', file: __dirname + '/my-program-icon.png', @@ -172,8 +173,8 @@ screen.render(); - [Special Elements](#special-elements) - [Terminal](#terminal-from-box) - [Image](#image-from-box) - - [PNG](#png-from-box) - - [W3MImage](#w3mimage-from-box) + - [ANSIImage](#ansiimage-from-box) + - [OverlayImage](#overlayimage-from-box) - [Video](#video-from-box) - [Layout](#layout-from-element) @@ -1395,43 +1396,43 @@ manager. Requires term.js and pty.js to be installed. See #### Image (from Box) Display an image in the terminal (jpeg, png, gif) using either blessed's -internal png/gif-to-terminal renderer (using a [PNG element](#png-from-box)) or -using `w3mimgdisplay` (using a [W3MImage element](#w3mimage-from-box)). +internal png/gif-to-terminal renderer (using a [ANSIImage element](#ansiimage-from-box)) or +using `w3mimgdisplay` (using a [OverlayImage element](#overlayimage-from-box)). ##### Options: - Inherits all from Box. - __file__ - Path to image. -- __itype__ - `ansi` or `w3m`. Whether to render the file as ANSI art or using - `w3m` to overlay Internally uses the PNG element. See the [PNG - element](#png-from-box) for more information/options. (__default__: `ansi`). +- __itype__ - `ansi` or `overlay`. Whether to render the file as ANSI art or + using `w3m` to overlay. See the [ANSIImage element](#ansiimage-from-box) for + more information/options. (__default__: `ansi`). ##### Properties: - Inherits all from Box. -- See [PNG element](#png-from-box) -- See [W3MImage element](#w3mimage-from-box) +- See [ANSIImage element](#ansiimage-from-box) +- See [OverlayImage element](#overlayimage-from-box) ##### Events: - Inherits all from Box. -- See [PNG element](#png-from-box) -- See [W3MImage element](#w3mimage-from-box) +- See [ANSIImage element](#ansiimage-from-box) +- See [OverlayImage element](#overlayimage-from-box) ##### Methods: - Inherits all from Box. -- See [PNG element](#png-from-box) -- See [W3MImage element](#w3mimage-from-box) +- See [ANSIImage element](#ansiimage-from-box) +- See [OverlayImage element](#overlayimage-from-box) -#### PNG (from Box) +#### ANSIImage (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. +internal PNG/GIF parser and does not require external dependencies. -Blessed uses an internal from-scratch PNG reader because no other javascript +Blessed uses an internal from-scratch PNG/GIF reader because no other javascript PNG reader supports Adam7 interlaced images (much less pass the png test suite). @@ -1452,12 +1453,12 @@ installed. ##### Options: - Inherits all from Box. -- __file__ - URL or path to PNG file. Can also be a buffer. +- __file__ - URL or path to PNG/GIF 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 + the image as it scales down to the proper number of cells. __NOTE__: PNG/GIF'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 @@ -1494,7 +1495,7 @@ installed. - __clearImage()__ - Clear the image. -#### W3MImage (from Box) +#### OverlayImage (from Box) Display an image in the terminal (jpeg, png, gif) using w3mimgdisplay. Requires w3m to be installed. X11 required: works in xterm, urxvt, and possibly other @@ -1505,7 +1506,7 @@ terminals. - Inherits all from Box. - __file__ - Path to image. - __ansi__ - Render the file as ANSI art instead of using `w3m` to overlay - Internally uses the PNG element. See the [PNG element](#png-from-box) for + Internally uses the ANSIImage element. See the [ANSIImage element](#ansiimage-from-box) for more information/options. (Default: `true`). - __w3m__ - Path to w3mimgdisplay. If a proper `w3mimgdisplay` path is not given, blessed will search the entire disk for the binary. diff --git a/lib/widget.js b/lib/widget.js index a2a13f4..7e3cd2f 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -37,8 +37,8 @@ widget.classes = [ 'ListTable', 'Terminal', 'Image', - 'PNG', - 'W3MImage', + 'ANSIImage', + 'OverlayImage', 'Video', 'Layout' ]; diff --git a/lib/widgets/image.js b/lib/widgets/image.js index 8e13052..7156bd1 100644 --- a/lib/widgets/image.js +++ b/lib/widgets/image.js @@ -29,25 +29,25 @@ function Image(options) { Box.call(this, options); - if (options.itype === 'ansi' && this.type !== 'png') { - var PNG = require('./png'); - Object.getOwnPropertyNames(PNG.prototype).forEach(function(key) { + if (options.itype === 'ansi' && this.type !== 'ansiimage') { + var ANSIImage = require('./ansiimage'); + Object.getOwnPropertyNames(ANSIImage.prototype).forEach(function(key) { if (key === 'type') return; Object.defineProperty(this, key, - Object.getOwnPropertyDescriptor(PNG.prototype, key)); + Object.getOwnPropertyDescriptor(ANSIImage.prototype, key)); }, this); - PNG.call(this, options); + ANSIImage.call(this, options); return this; } - if (options.itype === 'w3m' && this.type !== 'w3mimage') { - var W3MImage = require('./w3mimage'); - Object.getOwnPropertyNames(W3MImage.prototype).forEach(function(key) { + if (options.itype === 'overlay' && this.type !== 'overlayimage') { + var OverlayImage = require('./overlayimage'); + Object.getOwnPropertyNames(OverlayImage.prototype).forEach(function(key) { if (key === 'type') return; Object.defineProperty(this, key, - Object.getOwnPropertyDescriptor(W3MImage.prototype, key)); + Object.getOwnPropertyDescriptor(OverlayImage.prototype, key)); }, this); - W3MImage.call(this, options); + OverlayImage.call(this, options); return this; } } diff --git a/lib/widgets/png.js b/lib/widgets/png.js deleted file mode 100644 index 1e6af9a..0000000 --- a/lib/widgets/png.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * 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; - this.file = typeof file === 'string' ? file : null; - if (/^https?:/.test(file)) { - file = PNG.curl(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(file, { - colors: colors, - width: width, - height: height, - scale: this.scale, - ascii: this.options.ascii, - speed: this.options.speed, - filename: this.file - }); - 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; diff --git a/lib/widgets/w3mimage.js b/lib/widgets/w3mimage.js deleted file mode 100644 index 1fefda2..0000000 --- a/lib/widgets/w3mimage.js +++ /dev/null @@ -1,725 +0,0 @@ -/** - * w3mimage.js - w3m image element for blessed - * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License). - * https://github.com/chjj/blessed - */ - -/** - * Modules - */ - -var fs = require('fs') - , cp = require('child_process'); - -var helpers = require('../helpers'); - -var Node = require('./node'); -var Box = require('./box'); - -/** - * W3MImage - * Good example of w3mimgdisplay commands: - * https://github.com/hut/ranger/blob/master/ranger/ext/img_display.py - */ - -function W3MImage(options) { - var self = this; - - if (!(this instanceof Node)) { - return new W3MImage(options); - } - - options = options || {}; - - Box.call(this, options); - - if (options.w3m) { - W3MImage.w3mdisplay = options.w3m; - } - - if (W3MImage.hasW3MDisplay == null) { - if (fs.existsSync(W3MImage.w3mdisplay)) { - W3MImage.hasW3MDisplay = true; - } else if (options.search !== false) { - var file = helpers.findFile('/usr', 'w3mimgdisplay') - || helpers.findFile('/lib', 'w3mimgdisplay') - || helpers.findFile('/bin', 'w3mimgdisplay'); - if (file) { - W3MImage.hasW3MDisplay = true; - W3MImage.w3mdisplay = file; - } else { - W3MImage.hasW3MDisplay = false; - } - } - } - - this.on('hide', function() { - self._lastFile = self.file; - self.clearImage(); - }); - - this.on('show', function() { - if (!self._lastFile) return; - self.setImage(self._lastFile); - }); - - this.on('detach', function() { - self._lastFile = self.file; - self.clearImage(); - }); - - this.on('attach', function() { - if (!self._lastFile) return; - self.setImage(self._lastFile); - }); - - this.onScreenEvent('resize', function() { - self._needsRatio = true; - }); - - // Get images to overlap properly. Maybe not worth it: - // this.onScreenEvent('render', function() { - // self.screen.program.flush(); - // if (!self._noImage) return; - // function display(el, next) { - // if (el.type === 'w3mimage' && el.file) { - // el.setImage(el.file, next); - // } else { - // next(); - // } - // } - // function done(el) { - // el.children.forEach(recurse); - // } - // function recurse(el) { - // display(el, function() { - // var pending = el.children.length; - // el.children.forEach(function(el) { - // display(el, function() { - // if (!--pending) done(el); - // }); - // }); - // }); - // } - // recurse(self.screen); - // }); - - this.onScreenEvent('render', function() { - self.screen.program.flush(); - if (!self._noImage) { - self.setImage(self.file); - } - }); - - if (this.options.file || this.options.img) { - this.setImage(this.options.file || this.options.img); - } -} - -W3MImage.prototype.__proto__ = Box.prototype; - -W3MImage.prototype.type = 'w3mimage'; - -W3MImage.w3mdisplay = '/usr/lib/w3m/w3mimgdisplay'; - -W3MImage.prototype.spawn = function(file, args, opt, callback) { - var screen = this.screen - , opt = opt || {} - , spawn = require('child_process').spawn - , ps; - - ps = spawn(file, args, opt); - - ps.on('error', function(err) { - if (!callback) return; - return callback(err); - }); - - ps.on('exit', function(code) { - if (!callback) return; - if (code !== 0) return callback(new Error('Exit Code: ' + code)); - return callback(null, code === 0); - }); - - return ps; -}; - -W3MImage.prototype.setImage = function(img, callback) { - var self = this; - - if (this._settingImage) { - this._queue = this._queue || []; - this._queue.push([img, callback]); - return; - } - this._settingImage = true; - - var reset = function(err, success) { - self._settingImage = false; - self._queue = self._queue || []; - var item = self._queue.shift(); - if (item) { - self.setImage(item[0], item[1]); - } - }; - - if (W3MImage.hasW3MDisplay === false) { - reset(); - if (!callback) return; - return callback(new Error('W3M Image Display not available.')); - } - - if (!img) { - reset(); - if (!callback) return; - return callback(new Error('No image.')); - } - - this.file = img; - - return this.getPixelRatio(function(err, ratio) { - if (err) { - reset(); - if (!callback) return; - return callback(err); - } - - return self.renderImage(img, ratio, function(err, success) { - if (err) { - reset(); - if (!callback) return; - return callback(err); - } - - if (self.shrink || self.options.autofit) { - delete self.shrink; - delete self.options.shrink; - self.options.autofit = true; - return self.imageSize(function(err, size) { - if (err) { - reset(); - if (!callback) return; - return callback(err); - } - - if (self._lastSize - && ratio.tw === self._lastSize.tw - && ratio.th === self._lastSize.th - && size.width === self._lastSize.width - && size.height === self._lastSize.height - && self.aleft === self._lastSize.aleft - && self.atop === self._lastSize.atop) { - reset(); - if (!callback) return; - return callback(null, success); - } - - self._lastSize = { - tw: ratio.tw, - th: ratio.th, - width: size.width, - height: size.height, - aleft: self.aleft, - atop: self.atop - }; - - self.position.width = size.width / ratio.tw | 0; - self.position.height = size.height / ratio.th | 0; - - self._noImage = true; - self.screen.render(); - self._noImage = false; - - reset(); - return self.renderImage(img, ratio, callback); - }); - } - - reset(); - if (!callback) return; - return callback(null, success); - }); - }); -}; - -W3MImage.prototype.renderImage = function(img, ratio, callback) { - var self = this; - - if (cp.execSync) { - callback = callback || function(err, result) { return result; }; - try { - return callback(null, this.renderImageSync(img, ratio)); - } catch (e) { - return callback(e); - } - } - - if (W3MImage.hasW3MDisplay === false) { - if (!callback) return; - return callback(new Error('W3M Image Display not available.')); - } - - if (!ratio) { - if (!callback) return; - return callback(new Error('No ratio.')); - } - - // clearImage unsets these: - var _file = self.file; - var _lastSize = self._lastSize; - return self.clearImage(function(err) { - if (err) return callback(err); - - self.file = _file; - self._lastSize = _lastSize; - - var opt = { - stdio: 'pipe', - env: process.env, - cwd: process.env.HOME - }; - - var ps = self.spawn(W3MImage.w3mdisplay, [], opt, function(err, success) { - if (!callback) return; - return err - ? callback(err) - : callback(null, success); - }); - - var width = self.width * ratio.tw | 0 - , height = self.height * ratio.th | 0 - , aleft = self.aleft * ratio.tw | 0 - , atop = self.atop * ratio.th | 0; - - var input = '0;1;' - + aleft + ';' - + atop + ';' - + width + ';' - + height + ';;;;;' - + img - + '\n4;\n3;\n'; - - self._props = { - aleft: aleft, - atop: atop, - width: width, - height: height - }; - - ps.stdin.write(input); - ps.stdin.end(); - }); -}; - -W3MImage.prototype.clearImage = function(callback) { - var self = this; - - if (cp.execSync) { - callback = callback || function(err, result) { return result; }; - try { - return callback(null, this.clearImageSync()); - } catch (e) { - return callback(e); - } - } - - if (W3MImage.hasW3MDisplay === false) { - if (!callback) return; - return callback(new Error('W3M Image Display not available.')); - } - - if (!this._props) { - if (!callback) return; - return callback(null); - } - - var opt = { - stdio: 'pipe', - env: process.env, - cwd: process.env.HOME - }; - - var ps = this.spawn(W3MImage.w3mdisplay, [], opt, function(err, success) { - if (!callback) return; - return err - ? callback(err) - : callback(null, success); - }); - - var width = this._props.width + 2 - , height = this._props.height + 2 - , aleft = this._props.aleft - , atop = this._props.atop; - - if (this._drag) { - aleft -= 10; - atop -= 10; - width += 10; - height += 10; - } - - var input = '6;' - + aleft + ';' - + atop + ';' - + width + ';' - + height - + '\n4;\n3;\n'; - - delete this.file; - delete this._props; - delete this._lastSize; - - ps.stdin.write(input); - ps.stdin.end(); -}; - -W3MImage.prototype.imageSize = function(callback) { - var self = this; - var img = this.file; - - if (cp.execSync) { - callback = callback || function(err, result) { return result; }; - try { - return callback(null, this.imageSizeSync()); - } catch (e) { - return callback(e); - } - } - - if (W3MImage.hasW3MDisplay === false) { - if (!callback) return; - return callback(new Error('W3M Image Display not available.')); - } - - if (!img) { - if (!callback) return; - return callback(new Error('No image.')); - } - - var opt = { - stdio: 'pipe', - env: process.env, - cwd: process.env.HOME - }; - - var ps = this.spawn(W3MImage.w3mdisplay, [], opt); - - var buf = ''; - - ps.stdout.setEncoding('utf8'); - - ps.stdout.on('data', function(data) { - buf += data; - }); - - ps.on('error', function(err) { - if (!callback) return; - return callback(err); - }); - - ps.on('exit', function() { - if (!callback) return; - var size = buf.trim().split(/\s+/); - return callback(null, { - raw: buf.trim(), - width: +size[0], - height: +size[1] - }); - }); - - var input = '5;' + img + '\n'; - - ps.stdin.write(input); - ps.stdin.end(); -}; - -W3MImage.prototype.termSize = function(callback) { - var self = this; - - if (cp.execSync) { - callback = callback || function(err, result) { return result; }; - try { - return callback(null, this.termSizeSync()); - } catch (e) { - return callback(e); - } - } - - if (W3MImage.hasW3MDisplay === false) { - if (!callback) return; - return callback(new Error('W3M Image Display not available.')); - } - - var opt = { - stdio: 'pipe', - env: process.env, - cwd: process.env.HOME - }; - - var ps = this.spawn(W3MImage.w3mdisplay, ['-test'], opt); - - var buf = ''; - - ps.stdout.setEncoding('utf8'); - - ps.stdout.on('data', function(data) { - buf += data; - }); - - ps.on('error', function(err) { - if (!callback) return; - return callback(err); - }); - - ps.on('exit', function() { - if (!callback) return; - - if (!buf.trim()) { - // Bug: w3mimgdisplay will sometimes - // output nothing. Try again: - return self.termSize(callback); - } - - var size = buf.trim().split(/\s+/); - - return callback(null, { - raw: buf.trim(), - width: +size[0], - height: +size[1] - }); - }); - - ps.stdin.end(); -}; - -W3MImage.prototype.getPixelRatio = function(callback) { - var self = this; - - if (cp.execSync) { - callback = callback || function(err, result) { return result; }; - try { - return callback(null, this.getPixelRatioSync()); - } catch (e) { - return callback(e); - } - } - - // XXX We could cache this, but sometimes it's better - // to recalculate to be pixel perfect. - if (this._ratio && !this._needsRatio) { - return callback(null, this._ratio); - } - - return this.termSize(function(err, dimensions) { - if (err) return callback(err); - - self._ratio = { - tw: dimensions.width / self.screen.width, - th: dimensions.height / self.screen.height - }; - - self._needsRatio = false; - - return callback(null, self._ratio); - }); -}; - -W3MImage.prototype.renderImageSync = function(img, ratio) { - var self = this; - - if (W3MImage.hasW3MDisplay === false) { - throw new Error('W3M Image Display not available.'); - } - - if (!ratio) { - throw new Error('No ratio.'); - } - - // clearImage unsets these: - var _file = this.file; - var _lastSize = this._lastSize; - - this.clearImageSync(); - - this.file = _file; - this._lastSize = _lastSize; - - var width = this.width * ratio.tw | 0 - , height = this.height * ratio.th | 0 - , aleft = this.aleft * ratio.tw | 0 - , atop = this.atop * ratio.th | 0; - - var input = '0;1;' - + aleft + ';' - + atop + ';' - + width + ';' - + height + ';;;;;' - + img - + '\n4;\n3;\n'; - - this._props = { - aleft: aleft, - atop: atop, - width: width, - height: height - }; - - try { - cp.execFileSync(W3MImage.w3mdisplay, [], { - env: process.env, - encoding: 'utf8', - input: input, - timeout: 1000 - }); - } catch (e) { - ; - } - - return true; -}; - -W3MImage.prototype.clearImageSync = function() { - if (W3MImage.hasW3MDisplay === false) { - throw new Error('W3M Image Display not available.'); - } - - if (!this._props) { - return false; - } - - var width = this._props.width + 2 - , height = this._props.height + 2 - , aleft = this._props.aleft - , atop = this._props.atop; - - if (this._drag) { - aleft -= 10; - atop -= 10; - width += 10; - height += 10; - } - - var input = '6;' - + aleft + ';' - + atop + ';' - + width + ';' - + height - + '\n4;\n3;\n'; - - delete this.file; - delete this._props; - delete this._lastSize; - - try { - cp.execFileSync(W3MImage.w3mdisplay, [], { - env: process.env, - encoding: 'utf8', - input: input, - timeout: 1000 - }); - } catch (e) { - ; - } - - return true; -}; - -W3MImage.prototype.imageSizeSync = function() { - var img = this.file; - - if (W3MImage.hasW3MDisplay === false) { - throw new Error('W3M Image Display not available.'); - } - - if (!img) { - throw new Error('No image.'); - } - - var buf = ''; - var input = '5;' + img + '\n'; - - try { - buf = cp.execFileSync(W3MImage.w3mdisplay, [], { - env: process.env, - encoding: 'utf8', - input: input, - timeout: 1000 - }); - } catch (e) { - ; - } - - var size = buf.trim().split(/\s+/); - - return { - raw: buf.trim(), - width: +size[0], - height: +size[1] - }; -}; - -W3MImage.prototype.termSizeSync = function(_, recurse) { - if (W3MImage.hasW3MDisplay === false) { - throw new Error('W3M Image Display not available.'); - } - - var buf = ''; - - try { - buf = cp.execFileSync(W3MImage.w3mdisplay, ['-test'], { - env: process.env, - encoding: 'utf8', - timeout: 1000 - }); - } catch (e) { - ; - } - - if (!buf.trim()) { - // Bug: w3mimgdisplay will sometimes - // output nothing. Try again: - recurse = recurse || 0; - if (++recurse === 5) { - throw new Error('Term size not determined.'); - } - return this.termSizeSync(_, recurse); - } - - var size = buf.trim().split(/\s+/); - - return { - raw: buf.trim(), - width: +size[0], - height: +size[1] - }; -}; - -W3MImage.prototype.getPixelRatioSync = function() { - var self = this; - - // XXX We could cache this, but sometimes it's better - // to recalculate to be pixel perfect. - if (this._ratio && !this._needsRatio) { - return this._ratio; - } - this._needsRatio = false; - - var dimensions = this.termSizeSync(); - - this._ratio = { - tw: dimensions.width / this.screen.width, - th: dimensions.height / this.screen.height - }; - - return this._ratio; -}; - -W3MImage.prototype.displayImage = function(callback) { - return this.screen.displayImage(this.file, callback); -}; - -/** - * Expose - */ - -module.exports = W3MImage;