├── .gitignore ├── README.md ├── bin └── asciimage.js ├── example ├── cross.asciimage └── example.png ├── package.json └── src ├── cocoscii.js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | jspm_packages 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-cocoscii 2 | 3 | This is a Node.js version of the [javascript (ES2015) version](https://github.com/mrspeaker/cocoscii) of [ASCIImage](http://asciimage.org/) by [Charles Parnot](http://twitter.com/cparnot). ASCIImage lets you create icons by defining some control points for drawing lines, paths, and circles. You can also style each shape individually. [Read all about the original project](http://cocoamine.net/blog/2015/03/20/replacing-photoshop-with-nsstring/), then read about [cocoscii](https://github.com/mrspeaker/cocoscii), then come back here. 4 | 5 | # Try it out! 6 | 7 | `node-cocoscii` includes a commandline tool `bin/saciimage.js` for translating .asciimage files into png. It accepts .asciimage data via stdin, and outputs to its stdout a PNG image representation of the image. 8 | 9 | ``` 10 | npm install -g node-cocoscii 11 | cat ./examples/cross.asciimage | asciimage.js | open -f -a /Applications/Preview.app 12 | ``` 13 | 14 | You'll see something like as follows: 15 | 16 | ![Example .asciimage translated to .png](example/example.png) 17 | 18 | 19 | There are also a number of other implementations of ASCIImage available at [ASCIImage.org](http://asciimage.org). 20 | -------------------------------------------------------------------------------- /bin/asciimage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var canvas = require('canvas'); 5 | var System = require('es6-module-loader').System; 6 | 7 | var imgData = fs.readFileSync('/dev/stdin').toString(); 8 | 9 | System.import('./node_modules/cocoscii/src/cocoscii').then(function(cocosciiModule) { 10 | var cocoscii = cocosciiModule.default; 11 | 12 | var img = cocoscii(imgData, function(idx, style) { 13 | if (idx === 0) { 14 | style.fill = "#000"; 15 | } else { 16 | style.stroke = "#fff"; 17 | } 18 | }); 19 | 20 | process.stdout.write(img.toBuffer()); 21 | }); 22 | -------------------------------------------------------------------------------- /example/cross.asciimage: -------------------------------------------------------------------------------- 1 | . · · · 1 1 1 · · · · 2 | · · 1 · · · · · 1 · · 3 | · 1 · · · · · · · 1 · 4 | 1 · · 2 · · · 3 · · 1 5 | 1 · · · # · # · · · 1 6 | 1 · · · · # · · · · 1 7 | 1 · · · # · # · · · 1 8 | 1 · · 3 · · · 2 · · 1 9 | · 1 · . · . · . · 1 · 10 | · · 1 · . · . · . · · 11 | · · · 1 1 1 1 1 · · · 12 | -------------------------------------------------------------------------------- /example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz2/node-cocoscii/c58ebe1bce3c8cd2e89ba379ec4d00808ec1f34f/example/example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-cocoscii", 3 | "version": "1.0.0", 4 | "description": "Node.js version of cocoscii.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:mz2/cocoscii.git" 12 | }, 13 | "keywords": [ 14 | "asciimage", 15 | "ascii", 16 | "ascii", 17 | "art" 18 | ], 19 | "author": "Matias Piipari ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/mz2/cocoscii/issues" 23 | }, 24 | "homepage": "https://github.com/mz2/cocoscii", 25 | "dependencies": { 26 | "canvas": "^1.2.1", 27 | "es6-module-loader": "^0.15.0", 28 | "cocoscii": "https://github.com/mz2/cocoscii/tarball/master" 29 | }, 30 | "bin": { 31 | "asciimage.js":"bin/asciimage.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/cocoscii.js: -------------------------------------------------------------------------------- 1 | var Canvas = require('canvas'); 2 | var Image = Canvas.Image; 3 | 4 | /* 5 | rep: A "\n"-seperated ascii representation of the image. 6 | styles: A function to mutate the style dictionary. (shapeIndex: Number, dictionary: Object) => {} 7 | scale: Factor to scale the final image by. 8 | */ 9 | function cocoscii (rep, styles = (idx, dict) => {}, scale = 4) { 10 | 11 | const order = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnpqrstuvwxyz"; 12 | 13 | const rows = rep 14 | .split("\n") 15 | .filter(r => !r.match(/^\s*$/)) 16 | .map(r => r.split("").filter(ch => ch != " ")); 17 | 18 | const width = rows.reduce((max, row) => Math.max(max, row.length), 0); 19 | const height = rows.length; 20 | 21 | // Get the control points 22 | const points = rows 23 | .map((r, y) => r.map((ch, x) => { return { 24 | idx: order.indexOf(ch), 25 | ch, x, y 26 | }})) 27 | .reduce((flt, r) => [...flt, ...r], []) // Flatten 28 | .filter(p => p.idx != -1) 29 | .sort((p1, p2) => p1.idx - p2.idx); 30 | 31 | // Turn them into shapes 32 | const shapes = makeShapes(points); 33 | 34 | // ...and draw them 35 | const canvas = drawShapes(shapes, styles, width, height, scale); 36 | 37 | // Save to image 38 | //const img = new Image(); 39 | //img.src = canvas.toDataURL("image/png"); 40 | return canvas; 41 | } 42 | 43 | function makeShapes ([head, ...tail]) { 44 | 45 | function newShape (p) { 46 | return { 47 | type: "dot", 48 | points: [p] 49 | }; 50 | } 51 | 52 | const shapes = tail.reduce(({cur, shapes}, p) => { 53 | 54 | const prev = cur.points.slice(-1)[0]; 55 | 56 | // Another point in the path 57 | if (p.idx === prev.idx + 1 && ["dot", "path"].indexOf(cur.type) != -1) { 58 | cur.points.push(p); 59 | cur.type = "path"; 60 | } 61 | // Line or circle 62 | else if (p.idx === prev.idx) { 63 | cur.points.push(p); 64 | cur.type = cur.points.length < 3 ? "line" : "circle"; 65 | } 66 | // New shape 67 | else { 68 | shapes.push(cur); 69 | cur = newShape(p); 70 | } 71 | 72 | return {cur, shapes}; 73 | 74 | }, {cur: newShape(head), shapes:[]}); 75 | 76 | // Don't forget final shape! 77 | return [...shapes.shapes, shapes.cur]; 78 | 79 | } 80 | 81 | function drawShapes (shapes, styles, width, height, scale) { 82 | 83 | let styleDict = { 84 | fill: "#000", 85 | stroke: "", 86 | lineWidth: "1" 87 | }; 88 | 89 | const canvas = new Canvas(); 90 | const ctx = canvas.getContext("2d"); 91 | 92 | canvas.width = width * scale; 93 | canvas.height = height * scale; 94 | 95 | function getBBox ([{x, y}, ...tail]) { 96 | return tail.reduce(({min, max}, {x, y}) => { 97 | if (x < min.x) min.x = x; 98 | if (x > max.x) max.x = x; 99 | if (y < min.y) min.y = y; 100 | if (y > max.y) max.y = y; 101 | return {min, max}; 102 | }, {min: {x, y}, max: {x, y}}); 103 | } 104 | 105 | ctx.save(); 106 | ctx.scale(scale, scale); 107 | 108 | shapes.forEach(({type, points}, i) => { 109 | 110 | styles(i, styleDict); // Apply function to mutate the shape's styles 111 | 112 | const [{x, y}, ...tail] = points; 113 | const {fill, stroke, lineWidth} = styleDict; 114 | 115 | ctx.fillStyle = fill; 116 | ctx.strokeStyle = stroke; 117 | ctx.lineWidth = lineWidth; 118 | 119 | switch (type) { 120 | 121 | case "dot": 122 | // TODO: positioning is not correct. See "gridImg" test case 123 | const args = [x, y, 1 / width, 1 / height]; 124 | if (fill) ctx.fillRect(...args); 125 | if (stroke) ctx.strokeRect(...args); 126 | break; 127 | 128 | case "line": 129 | case "path": 130 | ctx.beginPath(); 131 | ctx.moveTo(x, y); 132 | tail.forEach(({x, y}) => ctx.lineTo(x, y)); 133 | 134 | if (fill) ctx.fill(); 135 | if (stroke) ctx.stroke(); 136 | break; 137 | 138 | case "circle": 139 | // Get "bounding box" of the circle 140 | const {min, max} = getBBox(points); 141 | const [w, h] = [max.x - min.x, max.y - min.y]; 142 | const circ = Math.max(w, h); 143 | 144 | // Draw an elipse to fit 145 | ctx.save(); 146 | ctx.translate(min.x + w / 2, min.y + h / 2); 147 | ctx.scale(w / circ, h / circ); 148 | ctx.beginPath(); 149 | ctx.arc(0, 0, circ / 2, 0, Math.PI * 2, false); 150 | ctx.restore(); 151 | 152 | if (fill) ctx.fill(); 153 | if (stroke) ctx.stroke(); 154 | break; 155 | 156 | } 157 | 158 | }); 159 | 160 | ctx.restore(); 161 | 162 | return canvas; 163 | 164 | } 165 | 166 | export default cocoscii; 167 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import cocoscii from "./cocoscii"; 2 | 3 | 4 | const closeImg = cocoscii(` 5 | · · · 1 1 1 · · · · 6 | · · 1 · · · · · 1 · · 7 | · 1 · · · · · · · 1 · 8 | 1 · · 2 · · · 3 · · 1 9 | 1 · · · # · # · · · 1 10 | 1 · · · · # · · · · 1 11 | 1 · · · # · # · · · 1 12 | 1 · · 3 · · · 2 · · 1 13 | · 1 · · · · · · · 1 · 14 | · · 1 · · · · · · · · 15 | · · · 1 1 1 1 1 · · · 16 | `, (idx, style) => { 17 | 18 | if (idx === 0) { 19 | style.fill = "#000"; 20 | } else { 21 | style.stroke = "#fff"; 22 | } 23 | 24 | }); 25 | 26 | const lockImg = cocoscii(` 27 | · · · · · · · · · · · · · · · 28 | · · · · 1 · · · · · · 1 · · · 29 | · · · · · · · · · · · · · · · 30 | · · · · · · · · · · · · · · · 31 | · · · · · · · · · · · · · · · 32 | · · 3 · 1 · · · · · · 1 · 4 · 33 | · · · · · · · · · · · · · · · 34 | · · · · · · A · · A · · · · · 35 | · · · · 1 · · · · · · 1 · · · 36 | · · · · · · · C D · · · · · · 37 | · · · · · · A · · A · · · · · 38 | · · · · · · · · · · · · · · · 39 | · · · · · · · B E · · · · · · 40 | · · · · · · · · · · · · · · · 41 | · · 6 · · · · · · · · · · 5 · 42 | `, (idx, style) => { 43 | 44 | if (idx === 0) { 45 | style.fill = ""; 46 | style.stroke = "#000"; 47 | } else if (idx === 1){ 48 | style.fill = "#000" 49 | style.stroke = ""; 50 | } else { 51 | style.fill = "#fff"; 52 | } 53 | 54 | }); 55 | 56 | // Uh oh, we got a problem. Not pixel aligned. 57 | // args = [x + 3 / width, y + 3 / height, 1 / (width + 4), 1 / (height + 4)] 58 | // will fix it: buuut, not generalized. 59 | const gridImg = cocoscii(` 60 | 1 . 3 . 5 . 7 61 | . 9 . B . D . 62 | F . H . J . L 63 | . N . P . R . 64 | T . V . X . Z 65 | . b . d . f . 66 | h . j . l . n 67 | `, (idx, style) => { style.stroke="#000"; }, 8); 68 | 69 | document.body.appendChild(closeImg); 70 | document.body.appendChild(lockImg); 71 | document.body.appendChild(gridImg); 72 | 73 | export default {}; 74 | --------------------------------------------------------------------------------