├── ProcessingTextmodeEngine.pde ├── README.md ├── UNLICENSE.txt ├── data ├── Px437_IBM_BIOS-16.vlw └── face.png └── renderTextMode.pde /ProcessingTextmodeEngine.pde: -------------------------------------------------------------------------------- 1 | // Processing Textmode Engine 2 | // Version 0.1 (initial release) 3 | // by Don Miller / NO CARRIER 4 | // http://www.no-carrier.com 5 | // Have fun! :) 6 | 7 | PFont font; // textmode font 8 | PGraphics b; // screen buffer 9 | PImage img; // used for face.png 10 | 11 | int segSize; // segment size, see renderTextMode tab for (lots) more detail 12 | int display = 1; // choose which demo to display, starts with face image 13 | float tick; // counter, used for sin calcuations for shape rotation 14 | 15 | boolean doDraw = true; // draw or pause 16 | boolean blockMode = false; // block mode or ASCII mode 17 | boolean fps = false; // show FPS or not 18 | boolean textMode = true; // show buffer (stretched to fit screen) or textmode 19 | 20 | void setup() { 21 | size(1024, 768, P2D); // need to use P2D as renderer, as we use P3D for buffer 22 | img = loadImage("face.png"); 23 | noSmooth(); // keep it blocky :) 24 | noCursor(); // don't need this! 25 | initTextmode(); // set up buffer, load font for textmode output 26 | } 27 | 28 | void draw() { 29 | if (doDraw) { 30 | tick = tick + 0.01; 31 | 32 | helloWorld(); // let's show off the textmode engine, shall we? 33 | 34 | // render options 35 | if (textMode) { // choose how to render: 36 | background(0); 37 | renderTextMode(); // display textmode... 38 | } else { 39 | background(0); 40 | image(b, 0, 0, width, height); // ...or display buffer (stretched to fit screen) 41 | } 42 | if (fps) { 43 | fill(0); 44 | rect(0, 0, 32, 16); 45 | fill(255); 46 | text(int(frameRate), 0, 0); 47 | } 48 | } 49 | } 50 | 51 | void helloWorld() { 52 | b.beginDraw(); 53 | b.background(0); 54 | switch(display) { 55 | case 1: 56 | drawImage(); 57 | break; 58 | case 2: 59 | drawBox(); 60 | break; 61 | case 3: 62 | drawBoxFilled(); 63 | break; 64 | case 4: 65 | drawSphere(); 66 | break; 67 | } 68 | b.endDraw(); 69 | } 70 | 71 | void drawImage() // draws and rotates a face image 72 | { 73 | b.translate(b.width/2, b.height/2); 74 | b.rotateY((tick*100)*TWO_PI/360); 75 | b.translate(-img.width/2, -img.height/2); 76 | b.image(img, 0, 0); 77 | } 78 | 79 | void drawBox() { // draws a wireframe cube with simple lighting 80 | b.lights(); 81 | b.pushMatrix(); 82 | b.translate(b.width/2, b.height/2, 0); 83 | b.rotateY(tick); 84 | b.rotateX(-tick); 85 | b.noFill(); 86 | b.strokeWeight(6); 87 | b.stroke(255, 255, 0); 88 | b.box(100); 89 | b.popMatrix(); 90 | } 91 | 92 | void drawBoxFilled() { // draws a filled cube with a spotlight 93 | b.spotLight(0, 255, 0, b.width/2, b.height/2, 400, 0, 0, -1, PI/4, 2); 94 | b.pushMatrix(); 95 | b.translate(b.width/2, b.height/2, 0); 96 | b.rotateY(tick); 97 | b.rotateX(-tick); 98 | b.fill(255); 99 | b.strokeWeight(6); 100 | b.stroke(255, 255, 0); 101 | b.box(100); 102 | b.popMatrix(); 103 | } 104 | 105 | void drawSphere() { // draws a filled sphere with a spotlight 106 | b.spotLight(255, 0, 0, b.width/2, b.height/2, 400, 0, 0, -1, PI/4, 2); 107 | b.pushMatrix(); 108 | b.translate(b.width/2, b.height/2, 0); 109 | b.rotateY(-tick); 110 | b.rotateX(+tick); 111 | b.strokeWeight(4); 112 | b.stroke(255, 165, 0); 113 | b.fill(255); 114 | b.sphereDetail(10); 115 | b.sphere(80); 116 | b.popMatrix(); 117 | } 118 | 119 | void keyPressed() { 120 | if (key == 'q') { 121 | fps = !fps; 122 | } 123 | if (key == 'a') { 124 | blockMode = !blockMode; 125 | } 126 | if (key == 'z') { 127 | textMode = !textMode; 128 | } 129 | if (key == 'p') { 130 | doDraw = !doDraw; 131 | } 132 | if (key == ' ') { // this is the spacebar ;) 133 | display++; 134 | if (display > 4) { 135 | display = 1; 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Processing Textmode Engine 2 | ## Version 0.1 (initial release) May 2016 3 | 4 | The Processing Textmode Engine renders output in [Processing](http://www.processing.org) to a full color text display, 5 | reminiscent of early computer or [BBS](https://en.wikipedia.org/wiki/Bulletin_board_system) graphics. I wrote it to use for realtime visual performance, but can be used to create movies, still images, or even games. 6 | It is free and open source. :v: 7 | 8 | Creating a textmode engine is something I've wanted to do for a long time. I 9 | grew up using [DOS](https://en.wikipedia.org/wiki/MS-DOS) and hanging out on BBSes, so I love textmode and [ANSI art](https://www.google.com/search?tbm=isch&q=ansi+art&gws_rd=ssl). I 10 | used [TheDraw](https://en.wikipedia.org/wiki/TheDraw) as a kid and now use [PabloDraw](http://picoe.ca/products/pablodraw/) for creating ANSI art. Even [Sixteen Colors](http://www.sixteencolors.net) has a [JS based textmode editor](http://draw.sixteencolors.net/). And as far as 11 | textmode converting goes, I've experimented with [AALib](http://aa-project.sourceforge.net/aalib/), [libcaca](http://caca.zoy.org/), and even [TextFX](http://sol.gfxile.net/textfx/index.html). 12 | I looked at the [Textmode Demo Framework](http://www.pouet.net/prod.php?which=64192) released by the [TMDC folks](http://tmdc.scene.org/), too. But I wanted 13 | something I could use with Processing, something fast and fun. And that's what 14 | this is. It may not be as accurate as the others, but I am hoping since it uses 15 | Processing (rather than C) that it may be more accessible to a wide range of 16 | artists and creative coders. 17 | 18 | The code is well commented, but here's the tl;dr on how it works: you draw to a small 19 | offscreen buffer and then convert the pixels in that buffer to text and draw to the 20 | main screen. Brightness controls the glyphs and the average color of the pixel area 21 | controls the color of that glyph. The background is always black, like conventional 22 | ANSI art tools. 23 | 24 | I hope you enjoy using it as much as I do! :pray: For more info, pics, and video, please 25 | visit: http://www.no-carrier.com 26 | 27 | Don Miller / NO CARRIER 28 | 29 | Click on the image below to be taken to Vimeo to check out the engine in action: 30 | [![Textmode Engine Demo Video](http://www.no-carrier.com/img/engineDemo.png)](https://vimeo.com/165805982) 31 | 32 | And here's a link to a video from a Textmode Lightsynth I'm working on. More soon :smile: 33 | [![Textmode Lightsynth Video](http://www.no-carrier.com/img/lightsynthVid.png)](https://vimeo.com/165815175) 34 | -------------------------------------------------------------------------------- /UNLICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /data/Px437_IBM_BIOS-16.vlw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-carrier/ProcessingTextmodeEngine/043a92490d166fcc0700c31da48317da97e75f8b/data/Px437_IBM_BIOS-16.vlw -------------------------------------------------------------------------------- /data/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-carrier/ProcessingTextmodeEngine/043a92490d166fcc0700c31da48317da97e75f8b/data/face.png -------------------------------------------------------------------------------- /renderTextMode.pde: -------------------------------------------------------------------------------- 1 | // math for comments below is based on a 1024x768px screen 2 | // with segSize variable set to 4, as you can see below in initTextmode() 3 | 4 | void initTextmode() { 5 | segSize = 4; 6 | // 800x600 / segSize of 4 = 200x150 buffer --- 1024x768 / segSize of 4 = 256x192 buffer 7 | // see renderTextMode about this whole buffer and segment size thing 8 | b = createGraphics(width/segSize, height/segSize, P3D); // we need P3D for our shapes 9 | // 16x16 IBM Bios font from: http://int10h.org/oldschool-pc-fonts/readme/ 10 | font = loadFont("Px437_IBM_BIOS-16.vlw"); 11 | textFont(font, 16); // size of font 12 | textAlign(LEFT, TOP); // helps line it all up 13 | } 14 | 15 | // the tl;dr for this whole damn tab is this: 16 | // we have a 256x192px off-screen buffer that we draw on 17 | // we break that up into 4x4px segments, for a total of 64x48 18 | // after we get the average RGB and brightness for each 4x4px segment 19 | // we assign a colored 16x16px glyph and write it to our 1024x768px screen 20 | 21 | // 1024x768 screen / 4 (for segment size) = 256x192px buffer 22 | // 256x192px / 4 = 64x48 segments of 4x4px each 23 | // 16x16px colored glyph assigned to each 64x48 4x4px segment = 1024x768px 24 | // MATH CHECKS OUT - WOOHOO! 25 | // (guess it wasn't really a tl;dr, but its shorter than what you see below...) 26 | 27 | 28 | void renderTextMode() { 29 | b.loadPixels(); // this loads pixel data from the buffer into an array for faster access. 30 | 31 | // now we have to go through all of the pixels, analyzing their color and brightness to 32 | // assign an appropriate color and glyph. our buffer is currently 256x192px. 33 | 34 | for (int h=0; h 204) { // and the brightness is above 204 100 | text("█", startX * segSize, startY * segSize); // we assign this character. 101 | } else if (segb > 152) { // if its above 152, we assign it 102 | text("▓", startX * segSize, startY * segSize); // this character. and so on... 103 | } else if (segb > 100) { 104 | text("▒", startX * segSize, startY * segSize); 105 | } else if (segb > 48) { 106 | text("░", startX * segSize, startY * segSize); // and if the brightness for our segment 107 | } // is under 48, we don't assign a glyph. 108 | } else { // here is our ASCII mode, 109 | if (segb > 230) { // where we do the same as above, 110 | text("#", startX * segSize, startY * segSize); // but we more characters! 111 | } else if (segb > 207) { 112 | text("&", startX * segSize, startY * segSize); 113 | } else if (segb > 184) { 114 | text("$", startX * segSize, startY * segSize); 115 | } else if (segb > 161) { 116 | text("X", startX * segSize, startY * segSize); 117 | } else if (segb > 138) { 118 | text("x", startX * segSize, startY * segSize); 119 | } else if (segb > 115) { 120 | text("=", startX * segSize, startY * segSize); 121 | } else if (segb > 92) { 122 | text("+", startX * segSize, startY * segSize); 123 | } else if (segb > 69) { 124 | text(";", startX * segSize, startY * segSize); 125 | } else if (segb > 46) { 126 | text(":", startX * segSize, startY * segSize); 127 | } else if (segb > 23) { 128 | text(".", startX * segSize, startY * segSize); // again, if brightness is less than 23, we 129 | } // don't assign a glyph, which makes it black. 130 | } 131 | } 132 | } 133 | } 134 | --------------------------------------------------------------------------------