diff --git a/css/ak.css b/css/ak.css new file mode 100644 index 0000000..8a2a8fd --- /dev/null +++ b/css/ak.css @@ -0,0 +1,139 @@ +.fa{color:#fff}.fb{color:#000}.fc{color:#00007F}.fd{color:#009300}.fe{color:red}.ff{color:#7f0000}.fg{color:#9C009C}.fh{color:#FC7F00}.fi{color:#FF0}.fj{color:#00FC00}.fk{color:#009393}.fl{color:#0FF}.fm{color:#0000FC}.fn{color:#F0F}.fo{color:#7F7F7F}.fp{color:#D2D2D2} +.ba{background-color:#fff}.bb{background-color:#000}.bc{background-color:#00007F}.bd{background-color:#009300}.be{background-color:red}.bf{background-color:#7f0000}.bg{background-color:#9C009C}.bh{background-color:#FC7F00}.bi{background-color:#FF0}.bj{background-color:#00FC00}.bk{background-color:#009393}.bl{background-color:#0FF}.bm{background-color:#0000FC}.bn{background-color:#F0F}.bo{background-color:#7F7F7F}.bp{background-color:#D2D2D2} + +.winxp .fa{color:rgb(255,255,255)} +.winxp .fb{color:rgb(0,0,0)} +.winxp .fc{color:rgb(0,0,128)} +.winxp .fd{color:rgb(0,128,0)} +.winxp .fe{color:rgb(255,0,0)} +.winxp .ff{color:rgb(128,0,0)} +.winxp .fg{color:rgb(128,0,128)} +.winxp .fh{color:rgb(255,128,0)} +.winxp .fi{color:rgb(255,255,0)} +.winxp .fj{color:rgb(0,255,0)} +.winxp .fk{color:rgb(0,128,128)} +.winxp .fl{color:rgb(0,255,255)} +.winxp .fm{color:rgb(0,0,255)} +.winxp .fn{color:rgb(255,0,255)} +.winxp .fo{color:rgb(128,128,128)} +.winxp .fp{color:rgb(192,192,192)} + +.winxp .ba{background-color:rgb(255,255,255)} +.winxp .bb{background-color:rgb(0,0,0)} +.winxp .bc{background-color:rgb(0,0,128)} +.winxp .bd{background-color:rgb(0,128,0)} +.winxp .be{background-color:rgb(255,0,0)} +.winxp .bf{background-color:rgb(128,0,0)} +.winxp .bg{background-color:rgb(128,0,128)} +.winxp .bh{background-color:rgb(255,128,0)} +.winxp .bi{background-color:rgb(255,255,0)} +.winxp .bj{background-color:rgb(0,255,0)} +.winxp .bk{background-color:rgb(0,128,128)} +.winxp .bl{background-color:rgb(0,255,255)} +.winxp .bm{background-color:rgb(0,0,255)} +.winxp .bn{background-color:rgb(255,0,255)} +.winxp .bo{background-color:rgb(128,128,128)} +.winxp .bp{background-color:rgb(192,192,192)} + +.vga .fa{color:rgb(255,255,255)} +.vga .fb{color:rgb(0,0,0)} +.vga .fc{color:rgb(0,0,170)} +.vga .fd{color:rgb(0,170,0)} +.vga .fe{color:rgb(255,85,85)} +.vga .ff{color:rgb(170,0,0)} +.vga .fg{color:rgb(170,0,170)} +.vga .fh{color:rgb(170,85,0)} +.vga .fi{color:rgb(255,255,85)} +.vga .fj{color:rgb(85,255,85)} +.vga .fk{color:rgb(0,170,170)} +.vga .fl{color:rgb(85,255,255)} +.vga .fm{color:rgb(85,85,255)} +.vga .fn{color:rgb(255,85,255)} +.vga .fo{color:rgb(85,85,85)} +.vga .fp{color:rgb(170,170,170)} + + +.vga .ba{background-color:rgb(255,255,255)} +.vga .bb{background-color:rgb(0,0,0)} +.vga .bc{background-color:rgb(0,0,170)} +.vga .bd{background-color:rgb(0,170,0)} +.vga .be{background-color:rgb(255,85,85)} +.vga .bf{background-color:rgb(170,0,0)} +.vga .bg{background-color:rgb(170,0,170)} +.vga .bh{background-color:rgb(170,85,0)} +.vga .bi{background-color:rgb(255,255,85)} +.vga .bj{background-color:rgb(85,255,85)} +.vga .bk{background-color:rgb(0,170,170)} +.vga .bl{background-color:rgb(85,255,255)} +.vga .bm{background-color:rgb(85,85,255)} +.vga .bn{background-color:rgb(255,85,255)} +.vga .bo{background-color:rgb(85,85,85)} +.vga .bp{background-color:rgb(170,170,170)} + +.c64 .fa{color:rgb(255,255,255)} +.c64 .fb{color:rgb(0,0,0)} +.c64 .fc{color:rgb(69,32,170)} +.c64 .fd{color:rgb(101,170,69)} +.c64 .fe{color:rgb(138,101,32)} +.c64 .ff{color:rgb(138,69,32)} +.c64 .fg{color:rgb(138,69,170)} +.c64 .fh{color:rgb(101,69,0)} +.c64 .fi{color:rgb(207,207,101)} +.c64 .fj{color:rgb(170,239,138)} +.c64 .fk{color:rgb(138,138,138)} +.c64 .fl{color:rgb(101,170,207)} +.c64 .fm{color:rgb(138,101,223)} +.c64 .fn{color:rgb(207,138,101)} +.c64 .fo{color:rgb(69,69,69)} +.c64 .fp{color:rgb(170,170,170)} + +.c64 .ba{background-color:rgb(255,255,255)} +.c64 .bb{background-color:rgb(0,0,0)} +.c64 .bc{background-color:rgb(69,32,170)} +.c64 .bd{background-color:rgb(101,170,69)} +.c64 .be{background-color:rgb(138,101,32)} +.c64 .bf{background-color:rgb(138,69,32)} +.c64 .bg{background-color:rgb(138,69,170)} +.c64 .bh{background-color:rgb(101,69,0)} +.c64 .bi{background-color:rgb(207,207,101)} +.c64 .bj{background-color:rgb(170,239,138)} +.c64 .bk{background-color:rgb(138,138,138)} +.c64 .bl{background-color:rgb(101,170,207)} +.c64 .bm{background-color:rgb(138,101,223)} +.c64 .bn{background-color:rgb(207,138,101)} +.c64 .bo{background-color:rgb(69,69,69)} +.c64 .bp{background-color:rgb(170,170,170)} + +.appleii .fa{color:rgb(255,255,255)} +.appleii .fb{color:rgb(0,0,0)} +.appleii .fc{color:rgb(64,53,121)} +.appleii .fd{color:rgb(64,75,7)} +.appleii .fe{color:rgb(191,180,248)} +.appleii .ff{color:rgb(109,41,64)} +.appleii .fg{color:rgb(218,60,241)} +.appleii .fh{color:rgb(218,104,15)} +.appleii .fi{color:rgb(191,202,134)} +.appleii .fj{color:rgb(38,195,16)} +.appleii .fk{color:rgb(19,87,64)} +.appleii .fl{color:rgb(146,214,191)} +.appleii .fm{color:rgb(37,151,240)} +.appleii .fn{color:rgb(236,168,191)} +.appleii .fo{color:rgb(128,128,128)} +.appleii .fp{color:rgb(128,128,128)} + +.appleii .ba{background-color:rgb(255,255,255)} +.appleii .bb{background-color:rgb(0,0,0)} +.appleii .bc{background-color:rgb(64,53,121)} +.appleii .bd{background-color:rgb(64,75,7)} +.appleii .be{background-color:rgb(191,180,248)} +.appleii .bf{background-color:rgb(109,41,64)} +.appleii .bg{background-color:rgb(218,60,241)} +.appleii .bh{background-color:rgb(218,104,15)} +.appleii .bi{background-color:rgb(191,202,134)} +.appleii .bj{background-color:rgb(38,195,16)} +.appleii .bk{background-color:rgb(19,87,64)} +.appleii .bl{background-color:rgb(146,214,191)} +.appleii .bm{background-color:rgb(37,151,240)} +.appleii .bn{background-color:rgb(236,168,191)} +.appleii .bo{background-color:rgb(128,128,128)} +.appleii .bp{background-color:rgb(128,128,128)} diff --git a/css/sally.css b/css/sally.css new file mode 100644 index 0000000..7d37f34 --- /dev/null +++ b/css/sally.css @@ -0,0 +1,255 @@ +textarea,input[type=text],body { + margin:0; + font-family: 'FixedsysExcelsior301Regular'; + font-size: 12pt; + font-weight: 100; + line-height: 11pt; + color: #6d6b6d; + -webkit-font-smoothing: antialiased !important; +} +body { + background-color: #000000 !important; +} +@font-face { + font-family: 'FixedsysExcelsior301Regular'; + src: url('../fonts/fsex300-webfont.eot'); + src: url('../fonts/fsex300-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/fsex300-webfont.woff') format('woff'), + url('../fonts/fsex300-webfont.ttf') format('truetype'), + url('../fonts/fsex300-webfont.svg#FixedsysExcelsior301Regular') format('svg'); + font-weight: normal; + font-style: normal; +} + +a {display: block} +a:link, a:visited {text-decoration: none; color: #6b6760} +a:hover { text-decoration: underline } + +.faded { color: #404040; } +.rapper, .block { + float: left; + height:auto; + width:auto; + background-color: #000000; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} +.rapper { + white-space:pre-wrap; + word-wrap: break-word; +} +#gallery_rapper { + display: inline +} +#ui_rapper .block { + width: 100px; +} +.block { + padding:4px; +} +.block:nth-child(n+2) { + padding-left: 30px; +} +#textarea_mode { padding: 4px; } +.tool { + cursor: pointer; +} +.hidden { + visibility: hidden; +} +.tool.radio { + margin: 0 8px 0 0; +} +.tool.radio.focused { + color: #000; + + background-color: #6d6d6d; + box-shadow: none; +} +.transparent { + background-color: transparent; + background-image: url(../img/gray-dither.gif); + background-size: 8px 8px; +} + +@media (-webkit-min-device-pixel-ratio: 2) { + .transparent { + background-size: 4px 4px; + } +} +span,a { min-width: 8px; line-height: 15px; display: inline-block; } +body.pixels { + line-height: 8px; +} +.pixels #brush_rapper span, +.pixels #brush_rapper a, +.pixels #canvas_rapper span, +.pixels #canvas_rapper a { line-height: 8px; overflow: hidden; } +.rapper { cursor: crosshair; } +body.grid span { border-right: 1px solid #444; border-bottom: 1px solid #444; } +body.grid div { border-left: 1px solid #444; } +body.grid #canvas_rapper > div:first-child, +body.grid #palette_rapper > div:first-child, +body.grid #letters_rapper > div:first-child, +body.grid #brush_rapper > div:first-child { border-top: 1px solid #444; } +body.grid .tool { border: 1px solid #444; } +.ed { color: #fff; } +.locked { border-bottom: 1px solid; color: #bbb; text-decoration: none; } +.tool.locked.focused { box-shadow: 0 0; } +.focused { box-shadow: inset 0 0px 2px #fff; border-color: #fff; } +.ba.focused { box-shadow: inset 0 0px 2px #000, inset 0 0px 2px #000; border-color: #000; } +.tool.focused, .ed.focused { color: white; text-decoration: underline; } +.focused { box-shadow: inset 1px 0 2px white, inset -1px 0 2px white, inset 0 1px 2px white, inset 0 -1px 2px white; } +.faba.focused, .fbba.focused, .fcba.focused, .fdba.focused, .feba.focused, .ffba.focused, .fgba.focused, .fhba.focused, +.fiba.focused, .fjba.focused, .fkba.focused, .flba.focused, .fmba.focused, .fnba.focused, .foba.focused, .fpba.focused + { box-shadow: inset 1px 0 2px #888, inset -1px 0 2px #888, inset 0 1px 2px #888, inset 0 -1px 2px #888; } +body.loading { opacity: 0; } +body { transition: 0.1s linear; } +#import_textarea { font-size: 9pt; } +textarea { font-size:12pt; width: 37vw; height: 300px; background: #333; color: #0f0; border: 0; font-family: 'FixedsysExcelsior301Regular'; outline: 0; border: 1px solid #333; background:#010;} +#shader_rapper { display: none; } +#import_rapper { display: none; } +#canvas_rapper { + white-space: pre; + box-shadow: 0 0 2px rgba(255,255,255,0.3); + margin: 3px; +} + +#ui_rapper { clear:both; float: left; width: 100vw; } +#workspace_rapper { width: 100%; } + +.loading .vertical #ui_rapper { clear: none } +.vertical #ui_rapper { width: 320px; float: left; clear: none; } +.vertical .rapper, .vertical .block { float: left; } +.vertical #canvas_rapper, +.vertical #canvas_rapper div, +.vertical #tools_rapper, +.vertical #palette_rapper, +.vertical #brush_container { display: inline-block; float: left; } +.vertical #workspace_rapper { width: auto; position: relative; float: left; } +.vertical #palette_rapper { margin-right: 10px; } +.vertical #tools_block { min-width: 100%; } + +#secret_rapper { float: left; clear: right; } +#secret_rapper span { float: left; } +.vertical #secret_rapper { margin-right: 10px; } +.vertical #secret_rapper span { float: left; clear: both; } +.nopaint #brush_rapper { min-height: 70px; min-width: 50px; } + +#nopaint_rapper.hidden { + display: none; +} + +.rotated #canvas_rapper { + transform: translateX(-50%) translateY(-50%) translateZ(0) rotate(-90deg); + transform-origin: 50% 50%; + position: absolute; + left: 50%; +} + +#tools_block > * { + cursor: crosshair; +} +#brush_rapper, #letters_rapper { + cursor: crosshair; +} +.dropper #canvas_rapper { + cursor: url(../img/dropper.gif) 0 15, auto; +} +.bucket #canvas_rapper { + cursor: url(../img/bucket.png) 3 15, auto; +} +#brush_rapper { + border: 1px solid; + display: inline-block; + margin-bottom: 13px; + float: left; +} +#letters_rapper { + display: inline-block; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} +.close { position: absolute; top: 20px; right: 20px; z-index: 2; padding: 10px; background: black; cursor: pointer; } +#webcam_rapper { display: none; position: absolute; top: 0px; left: 0px; width:100%;height:100%; box-sizing:border-box; border: 40px solid rgba(0,0,0,0.5); background-color: rgba(0,0,0,0.5); } +#webcam_iframe { position: absolute; top: 0px; left: 0px; width:100%;height:100%; background-color: rgba(0,0,0,0.5); border: 0; } +#experimental_palette_toggle.focused { box-shadow: none; } +#cursor_input { position: fixed; top: 0; right: 0; width:30px; opacity: 0; font-size: 16px; } +.selector_el { + border: 1px dashed #fff !important; + padding-top: 1px; + position:absolute; + margin-top: -1px; + top:-999px;left:-999px; + pointer-events: none; +} +.selector_el.dragging { + color: #0f0; +} +.selector_el.creating div { + display: none; +} +.custom { + float: left; + margin-right: 5px; + margin-bottom: 5px; +} +#username_input { + background: transparent; + padding: 0; + outline: 0; + border: 1px solid transparent; + width: 76px; +} +#username_input:focus { + border: 1px solid #0f0; + color: #0f0; +} +#upload_input { + background: transparent; + padding: 0; + outline: 0; + border: 1px solid #0f0; + color: #0f0; + width: 100%; + max-width: 360px; +} +#upload_button.uploading { + background: transparent; + border: 0; + font-size: 16px; + font-family: 'FixedsysExcelsior301Regular'; + -webkit-animation: rainbow 1.0s infinite; + animation: rainbow 2.0s infinite; + padding: 0; margin: 0; +} +@keyframes rainbow { + 0% { color: hsl(0,100%,50%) } + 33% { color: hsl(90,100%,50%) } + 50% { color: #fff } + 66% { color: hsl(320,100%,50%) } + 100% { color: hsl(360,100%,50%) } +} + +.panke #send_to_irc_el { + color: white; + text-decoration: underline; +} +.panke #shader_el, +.panke #load_el, +.panke #gallery_el, +.panke #import_textarea, +.panke #doc_el, +.panke #gallery_el, +.panke #save_button, +.panke #upload_button, +.panke #export_button, +.panke #username_input, +.panke #upload_input, +.panke #grid_el, +.panke #save_el, +.panke #vertical_checkbox, +.panke #add_custom_el, +.panke #format_el { display: none !important; } diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..56c5094 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,38 @@ + + + + + + + +

+
+ + +
+

asdf.us/ascii documentation

+ + +These are some handy documents which address some of the more obscure +features of the asdf.us color code tool: + +* tips.txt - Tips on using the keyboard +* irssi.txt - Instructions on using IRSSI to make color codes. +* nopaint.txt - A guide to "No Paint" + +Documents on Shaders + +* shadetut.txt - A brief tutorial on ASCII shaders. +* shaders/brush.txt - Shaders designed to work on the brush +* shaders/canvas.txt - Shaders designed to work on the canvas +* shaders/util.txt - Miscellaneous utilities / snippets + +For more information on IRC, Color Codes, and much more, visit the +documentation sitemap, part of the Jollo IRC Network. + +asdf.us/ascii + diff --git a/doc/irssi.txt b/doc/irssi.txt new file mode 100644 index 0000000..baded1c --- /dev/null +++ b/doc/irssi.txt @@ -0,0 +1,153 @@ + __________________________________________________________________________ + ____ ____ _____ ____ ___ ___ ____ + /_____ _____/ / ___ \ / ____/ / ____/ /_____ _____/ + / / / / \ \ / / / / / / + / / / /____/ / \ \__ \ \__ / / + / / / ___ __/ \__ \ \__ \ / / + / / / / \ \ \ \ \ \ / / + _____/ /_____ / / \ \ ____/ / ____/ /____/ /_____ + ___/ /__/ /_______\ \__/ /___/ // /____ + __________________________________________________________________________ + + +OPTIMIZE YOUR TERMINAL FOR COLOR CODES ON OSX +============================================= + +You can use terminal, some nerds seem to prefer iterm2, but it's up to you +... http://iterm2.com/ + +To see color codes correctly, make sure your term type is xterm-256color -- + +If you use iterm: https://s3.amazonaws.com/luckyplop/a1b0f0e3d6eae746c82194876f2ccd8b200bc3bb.png +If you use terminal: https://s3.amazonaws.com/luckyplop/6a2270b58ea1cfac587607215e1b829f41d47355.png + +Restart iterm after changing this setting. + +The default iterm colors are kind of ugly for color codes, so you may want to change them +to something like this.. + +https://s3.amazonaws.com/luckyplop/c5f3a1f2b8e2f8a745fa2638c21af7d26117b91b.png + +You can download this iTerm color preset here: + +http://asdf.us/ascii/doc/bamboo.itermcolors + + +INSTALLING IRSSI ON OSX +======================= + +For me the easiest thing is to install homebrew >> http://brew.sh/ + +Follow les instructions and then.. + +brew install irssi + +Then you run irssi from a terminal by typing the magic word.. + +irssi + + +SETTING UP IRSSI FOR COLOR CODES +================================ + +Use these commands for proper unicode support -- + +/set term_charset utf-8 +/set recode_autodetect_utf8 ON +/set recode_fallback ISO-8859-15 +/set recode ON + +Use these commands to dump color codes quickly and efficiently -- + +/set cmd_queue_speed 0msec +/set cmds_max_at_once 1 +/set flood_max_msgs 0 +/set flood_timecheck 0 + +To make your log go back very far -- + +/set scrollback_lines 20000 +/set scrollback_time 10day + +Remember to type /save after doing a /set to save your changes! + +/save + +Your irssi configuration will be stored in your home directory in.. + +.irssi/config + + +NORMAL IRSSI OPERATION +====================== + +If you do not want to do the autojoin thing these are the commands you'd normally use to connect: + +/server -ssl irc.asdf.us 7777 +/join #ascii + + +SETTING UP IRSSI TO AUTOJOIN #ASCII +=================================== + +First run irssi, then paste in these commands. +Please change YOUR_NICK_HERE to your preferred username! + +/network add -nick YOUR_NICK_HERE -user YOUR_NICK_HERE -realname "YOUR NAME HERE" asdf +/server add -network asdf -auto -ssl irc.asdf.us 7777 +/channel add -auto #ascii asdf +/save +/quit + +Now run irssi again.. it should autoconnect to the channels and stuff. +If you want it to move you into #ascii by default, you can do ctrl-N and then + +/layout save +/save + + +IRC TIPS +======== + +/join #ascii -- join a channel :) +/part #ascii -- leave a channel ;( +/quit blabla -- quit irc (with the quit message 'blabla') +/list -- list channels +/nick booboo -- change your nick to booboo +/who #ascii -- show complete list of people on #ascii +/names -- show quick list of names +/msg nick blabla -- send someone a private message ;) + + +IRSSI TIPS +========== + +Ctrl-N -- move to next window +Ctrl-P -- move to previous window +/window close -- close a window +Fn-up arrow -- page up (if you don't have a pageup key) +Fn-down arrow -- page down (if you don't have a pagedown key) + + +IRSSI SCRIPTING +=============== + +This is its own can of worms. May we suggest: + +http://scripts.irssi.org/scripts/noticelogic.pl +http://scripts.irssi.org/scripts/nickcolor.pl + +To make it run on startup, copy it into ~/.irssi/scripts/autorun/ (and restart irssi) + +Another fun IRC thing is running an XDCC server for sharing files.. +-- for more info see http://asdf.us/xdcc/ + + +AND REMEMBER... +=============== + +Have fun and be safe online! + + + + diff --git a/doc/nopaint.txt b/doc/nopaint.txt new file mode 100644 index 0000000..5428cab --- /dev/null +++ b/doc/nopaint.txt @@ -0,0 +1,58 @@ + + "" 88 + 9,88m, ,8888, 9,88m, ,888, mm 9,88m, 8888 + 88 88 86 98 88 88 ,mm88 88 88 88 88 + 88 88 '8888' 88888' "nn89 88 88 88 "8m + 88 a brief tutorial :) + +Last month or so I encountered Jeffrey Scudder's tool 'No Paint' - + +https://www.nopaint.org/ + +- an automatic drawing tool with a minimal interface: you control it using +just two buttons. The No Paint tool provided much entertainment on #sally, +so during some downtime I added similar functionality to the asdf.us/ascii +tool. Under the brush, you should see two buttons - to kick it off, click +'paint' and it will begin drawing. + +If you don't like what it's doing, click 'no' - +- this will remove the current line and start drawing a new line. + +If you like what it's doing, click 'paint' - +- the line will be applied to the canvas, and it will start drawing anew. + +While it's going, you can also click 'pause' and it will stop, so you can +save it or draw on it yourself. + + +Keyboard shortcuts - + +left arrow - 'no' +right arrow - 'paint' +down arrow - 'pause' + + +Right-click toggles - + +If you RIGHT-CLICK on "Paint" it will switch tools automatically. + +If you RIGHT-CLICK on "No" it will engage TURBO MODE. + + +Some tools currently implemented - + +- solid brush +- erase brush +- color-changing brush +- hue brush +- letter brush +- clone tool +- smear tool +- fill tool +- stars brush +- canvas slide +- canvas scale +- canvas rotate +- canvas colorcycle + + diff --git a/doc/shaders/brush.txt b/doc/shaders/brush.txt new file mode 100644 index 0000000..8a5f672 --- /dev/null +++ b/doc/shaders/brush.txt @@ -0,0 +1,126 @@ +BRUSH SHADERS +============= + +Unless noted, these shaders were written to work on the brush itself. +Make sure "brush" is selected and "animate" is checked. + + +>> distressed texture brush + +Sample use of the "choice" function to get a random color. + +var char = choice(" abcdef ") +lex.bg = +choice("0124") +lex.fg = +choice("01234") +lex.char = char +lex.opacity = char == " " ? 0 : 1 + + + +>> foggy terrain brush + +var char = choice(" abcdef ") +lex.bg = choice([14,15]) +lex.fg = choice("367") +lex.char = char +lex.opacity = char == " " ? 0 : 1 + + + +>> mirror brush (left-right) + +NOTE: Animate this on the canvas, then draw: + +if (x > w/2) { + lex.assign( canvas.aa[y][w-x] ) +} + + + +>> mirror brush (up-down) + +NOTE: Animate this on the canvas, then draw: + +if (x > h/2) { + lex.assign( canvas.aa[h-y][x] ) +} + + + +>> rainbow stardust brush + +Uncheck BG and animate this to brush: + +lex.fg = hue(t) +lex.char = choice(" ,'.,.','****** ") + + + +>> noise brushes, works on a black background: + +lex.bg = max(5, yellow(randint(t))) +lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +>> simple rainbow: + +if (lex.bg != 1) lex.bg = randint(t) +lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +>> self-erasing: + +if (lex.bg != 1) lex.bg = yellow(randint(t)) +lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +>> cycling rainbow brush + +if (lex.bg != 1) lex.bg = hue( all_color_hue_order.indexOf( color_names[ lex.bg ] ) + 1 ) +lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +>> "stars" brush.. set your brush to paint just the character "#" + +if (lex.char == "#") { + lex.fg = hue(randint(15)) + lex.char = random() > 0.1 ? " " : "+@*.,\"+'*-"[randint(10)] +} + + + +>> use fg char to mask mask what you're drawing on the bg + +if (lex.char != "/") { lex.bg = 1 } + + + +>> sharded glitch brush + +Example: http://asdf.us/z/kksnvs.png + +Use on a brush: + +lex.bg = t/y/x +lex.opacity = lex.bg % 1 ? 0 : 1 + + + +>> incremental brush + +Set your brush to be the ^ character, square, about 10x10 +Draw "char" only +Then animate this shader on the canvas: + +if (lex.char=="^") { + lex.bg += 1 + lex.char = " " +} +lex.bg += 1 + + + diff --git a/doc/shaders/canvas.txt b/doc/shaders/canvas.txt new file mode 100644 index 0000000..3e036bd --- /dev/null +++ b/doc/shaders/canvas.txt @@ -0,0 +1,237 @@ +CANVAS SHADERS +============== + +These shaders were written to work on areas of canvas. +Make sure "canvas" is selected and "animate" is checked. + + +>> original shader.. + +lex.bg = hue((x+y*y+t/10)/20) +lex.fg = (x+y)%16 +lex.char = (y%2) ? ":" : "%" + + + +>> energy ball ascii shader + +d = dist(x/2+w/4, y, w/2, h/2) +an = angle(x/2+w/4, y, w/2,h/2)+t/4200 +r=10.2 + +if (d < r) lex.bg = randint(r) + +ll=abs(an|0)+"" +lex.char=ll[ll.length-1] + +if (d > r) { + lex.bg = randint(d) + lex.fg = randint(d) + lex.char = ll[ll.length-2] +} + + + +>> drifting fire + +t += sin(x/1000)*100000 +pos = y/h*6 + sin(x*3) - cos(y*t/10000-10) +pos = clamp(pos, 0, 6) +lex.bg = hue(pos) + + + +>> the "bJoel56" shader + +yy=y +x-=w/2 +y-=h/2 + +lex.bg = blue(yy/h+random()) +lex.fg = green(yy/h*4 + sin(x/100+random()/2)) // hue(t/1000)|0; + +var abcd=".'~:;!>+=icjtJYSGSXDQKHNWM"; +function chara (aa,n) { return aa[clamp(n*aa.length, 0, aa.length)|0] } +lex.char = chara(abcd, y/h*(5/3 + tan(x/100)+random()/1)) + + + +>> frog shader v2 + +t/=-100 +d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t)/2 ) * 10 + +lex.char=',./>"ASE$#'[(floor(d))] +lex.fg = [1,3,9][floor(d*3)%3] +lex.bg=1 + + + +>> frog shader v3 + +// set period to like 0.2 for a normal circle +period = y/10 + +t/=-100 +d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t) * period ) +dd = d * 10.5 +d3 = dd < 8 ? 0 : 1 + +lex.char=' .,"+/>OXEN'[(floor(dd))] +lex.fg = [3,9][floor(d3)] +lex.bg=1 + + + +>> spaceships + +many cool shaders are possible with this technique.. changing the char +gradient (lex.char=...) etc. i love how the dots move on v4. + +this is a variation that looks like a bunch of ships flying across the screen. +has a really cool 3d look to it cuz the rows move at different speeds. + +period = sin(y) + +t/=-100 +d = sinp( (dist(x/2+w/4, y, w/2, h/2) + t) * period ) +dd = d * 10.5 +d3 = dd < 8 ? 0 : 1 + +lex.char=' .,"+/>\^+'[(floor(dd))] +lex.fg = [3,9][floor(d3)] +lex.bg=1 + + + +>> concentric circles with a wavy "sunburst" pattern going around them + +x -= w/2 +y -= h/2 + +x /= h +y /= h/2 + 2 + +r = dist(x,y,0,0) +ang = angle(x,y,0,0) + +if (r < 0.6) { + if (abs(mod(sin((r*t)/100000000000) + ang*18,TWO_PI)) < 2) + lex.bg = 12 + else + lex.bg = 5 +} +else if (r < 0.65) + lex.bg = 4 +else if (abs(mod(sin((r*t)/100000000000) + ang*18,TWO_PI)) < 2) + lex.bg = 7 +else + lex.bg = 8 + + + +>> slash-based interference patterns + +if (x > h*2) x=h-x +y-=h/2 +t/=2000 + +if (sin(x-y*t) > 0) { + lex.bg=1 + lex.fg=4 + lex.char= Math.floor(x-y*10001+t)%2 ? '\/' : '\\' +} +else { + lex.bg=1 + lex.fg=9 + lex.char= Math.floor(3*x+y+t)%2 ? '\\' : ' ' +} + + + +>> sparkling stars + +if (lex.char != " ") { + lex.fg =floor( Math.random()*10 ) + var az="Xx+*" + lex.char=az[floor(az.indexOf(lex.char)+ t/10000000 +Math.random())%az.length] +} + + + +>> coogi x/y doodle + +xx=x +t/=1000 +x/=w/2 +y/=h/2 +y-=1 +x-=1 +x*=x-sin(y/t) +y*=1 + +lex.bg = 1 // gray( sin(x/(y/3-1)+t) + sin(y/4+t) ) +lex.fg = hue( sin((y/5)+t) - cos(x*t) *5 ) +lex.char = " _.,:;\"~| "[Math.round(xx*(y+1+(x+t/102)/4)*13)%13] + + + +>> glitch shader - produces odd combinations of fg/bg + +lex.char=String.fromCharCode(lex.char.charCodeAt(0)+1) +lex.bg+=7 +lex.fg+=5 + + + +>> dots / lines shader + +xx = ((t/10*x)*y/10)%8 +lex.bg = colors.black +lex.fg = green(x*3+y*5) +lex.char = ((xx%1) !== 0) ? " " : " .,;=+!@"[xx] + + + +>> munching squares horizon + +t/=100 +y+=10 +x-=w/2 +x/=y/10 +lex.bg=hue((x^y)+t) + + + +>> grayscale vertical interlacing + +First, make a canvas that's totally white. + +Run this shader: + +if (lex.bg == 0) { + lex.bg = ((x)%2) ? 15 : 14 +} + +Then set your brush to a white square. + +Run this shader w/ animate: + +if (lex.bg == 0) { + lex.bg = ((x)%2) ? 0 : 1 +} + + + +>> nice purple/orange texture + +lex.bg=colors.purple +lex.fg=colors.orange +x/=3 +x=floor(x+y/2.1) // <- this is cool number to change +if (x+10*sin(x)+10*cos(y/(t%100)) < y/3) { + lex.char="abcdefghijklmnopqrstuvwxyz"[x%26] +} else { + lex.char="abcdefghijklmnopqrstuvwxyz".toUpperCase()[x%26] +} + diff --git a/doc/shaders/util.txt b/doc/shaders/util.txt new file mode 100644 index 0000000..a455eb3 --- /dev/null +++ b/doc/shaders/util.txt @@ -0,0 +1,38 @@ +SHADER UTILITIES +================ + +These are little snippets which may be useful in writing your own shaders. + + + +>> basic way to slow the frame rate of a shader. + +window.zz=window.zz||0 +if(!(x+y)) zz++ +if (lex.bg != 1 && !(zz % 4)) { + ... +} + + +>> handy for brushes - use color to mask brush shape + +lex.opacity = lex.bg == colors.black ? 0 : 1 + + +Tip: Set to "animate brush", then use option+shift (alt+shift) to +copy color from the canvas. Brush will have the "shape" of the +copied color only. Can be a cool effect when used with fg/bg only. + + + +>> copy color from canvas at x/y + +lex.assign( canvas.get(x,y) ) + + + +>> animate canvas up and to the left.. + +lex.assign( canvas.get(x+1,y+1) ) + + diff --git a/doc/shadetut.txt b/doc/shadetut.txt new file mode 100644 index 0000000..681b421 --- /dev/null +++ b/doc/shadetut.txt @@ -0,0 +1,156 @@ +ASCII SHADER TUTORIAL +===================== + +In the asdf.us/ascii shaders, you write a little math function that executes on every +pixel on the selected area. The shaders can affect either the brush, the selected region, +or the whole canvas. + +Shaders can also be animated, so they update live. With a shader applied to the brush, +the brush changes continuously as you draw. + + +THE LEX OBJECT +============== + +Essentially you are writing a Javascript function that modifies this "lex" object, which +has four properties + +1) lex.bg = this is the background color +2) lex.fg = this is the foreground color (text color) +3) lex.char = this is the letter that you see in the space +4) lex.opacity = this is whether the pixel actually draws or not + - so like a circular brush is opacity 1 in the middle and opacity 0 on the corners + + +THE COLOR CODE NUMBERS +====================== + +With lex.bg and lex.fg, the goal is to have a number between 0 and 15, corresponding to +the color code values from mIRC. + +If you shift-click on the color palette, you can cycle it around to the one which shows +the actual order of the mIRC colors. + +The mIRC colors are the ones that go white, black, dark blue, green, red, dark red ... +and these correspond to the numbers 0, 1, 2, 3, 4 ... + + +COLOR CYCLING +============= + +Additionally there are some color functions that might help - +These functions make it easier to cycle through colors in a way that makes sense logically +(since the mIRC colors are in a weird order) + +- hue(...) = this creates a cycle of colors in terms of their hue or color name, + so you get a rainbow that goes from dark red through yellow, green, blue, + purple, and back +- gray(...) = cycles through grayscale +- red(...) yellow(...) green(...) blue(...) purple(...) = use smaller palettes +- inv_hue(...) fire(...) dark_gray(...) = these are oddities i made for fun + + +VARIABLES +========= + +Variables you have at your disposal are similar to the asdf.us/shader tool - + +- x, y = the coordinates of the pixel +- mouse.x, mouse.y = the coordinate of the mouse as it hovers over the canvas +- t = the current time, in milliseconds + +TIP: The time will increase very quickly - it's good to add t /= 1000 at the top of +your shader so it goes slowly (and won't cause a seizure). + + +FUNCTIONS +========= + +Remember, this is Javascript. You have the basic operators: + ++ - / * + +And the bitwise operators: + +& | ^ ~ + +You can do if statements with the standard comparison operators: + +< > == <= >= + +You also have access to all the functions on the Math object: + +floor, ceil, round +abs, sign, mod(n,m), xor +pow, exp, sqrt +cos, sin, tan +acos, asin, atan, atan2 +random() rand(n) randint(n) randrange(a,b) +E, PI, PHI + +And some utility functions which might help: + +clamp(n,min,max) +mix(n,a,b) (lerp) +step(a,b) +smoothstep(min,max,n) +avg(m,n,a) +cosp, sinp (mapped to [0,1]) +pixel(x,y) == 4*(y*w+h) +dist(x,y,a,b) +angle(x,y,a,b) +choice(array) +deg(radians), rad(degrees) + + +BEYOND BASIC COLORS +=================== + +Other weird effects are possible if you combine these color functions. + +For instance, if you do hue(x+y) you'll get a rainbow. But remember, this is just +outputting a number between 0 and 15. So you can do hue(x+y) + 1 and get a different +cycle which does not really have anything to do with the rainbow, but looks cool. + + +HOW DRAWING WORKS IN THE ASCII TOOL +=================================== + +When you click and drag to draw a line, your mouse produces a series of points which +describe the line you tried to draw. But these points do not necessarily make a +continuous line - more like a series of dots, which it then draw lines between to make +a "line" or "brush stroke". + +A line between two points is made by stamping the brush at regular intervals between the +points which, with these brushes, ends up filling the space in between so it looks like +you drew a continuous line. + +This is why when you draw a line with a big brush, it smears the outer edges.. The stamps +happen right next to each other, so you wind up seeing mostly brush edges. + +You can visualize this effect with the following shader: + +lex.bg = mouse.x + mouse.y + +Drawing strokes quickly, or slowly. +Make sure to make it animate to brush. +Results could look like this: + +http://i.asdf.us/im/f9/1458658781640-ascii-bamboo.png + + +SAMPLE SHADERS +============== + +You can see a list of example shaders here: + +http://asdf.us/ascii/doc/shaderz.txt + +If you make a cool shader and want to see it on the list, please get in touch! +You can find me on irc.jollo.org:9999 (ssl) in #sally, making color codes with my friends. + +Thanks and have fun! + +~ Bamboo, 22 Marzo 2016 + + diff --git a/doc/tips.txt b/doc/tips.txt new file mode 100644 index 0000000..d014197 --- /dev/null +++ b/doc/tips.txt @@ -0,0 +1,16 @@ +asdf.us/ascii tips +================== + +These keyboard commands work in brush mode (square, circle, cross): + +[ brush smaller +] brush bigger +ctrl~click on brush erase cell +ctrl~click on canvas draw with bg color +shift~click on canvas draw line from last position +alt~click on canvas fill brush with sampled color +alt~shift~click on canvas copy canvas to brush +rightclick on palette set bg color (when drawing with a letter) + +h/t timb for guide + diff --git a/fonts/fsex300-webfont.eot b/fonts/fsex300-webfont.eot new file mode 100644 index 0000000..e2e493b Binary files /dev/null and b/fonts/fsex300-webfont.eot differ diff --git a/fonts/fsex300-webfont.eot? b/fonts/fsex300-webfont.eot? new file mode 100644 index 0000000..e2e493b Binary files /dev/null and b/fonts/fsex300-webfont.eot? differ diff --git a/fonts/fsex300-webfont.svg b/fonts/fsex300-webfont.svg new file mode 100644 index 0000000..a99c895 --- /dev/null +++ b/fonts/fsex300-webfont.svg @@ -0,0 +1,6006 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/fsex300-webfont.ttf b/fonts/fsex300-webfont.ttf new file mode 100644 index 0000000..d53e501 Binary files /dev/null and b/fonts/fsex300-webfont.ttf differ diff --git a/fonts/fsex300-webfont.woff b/fonts/fsex300-webfont.woff new file mode 100644 index 0000000..a2225f9 Binary files /dev/null and b/fonts/fsex300-webfont.woff differ diff --git a/img/bucket.png b/img/bucket.png new file mode 100644 index 0000000..cfaab30 Binary files /dev/null and b/img/bucket.png differ diff --git a/img/dropper.gif b/img/dropper.gif new file mode 100644 index 0000000..7d9eee1 Binary files /dev/null and b/img/dropper.gif differ diff --git a/img/gray-dither.gif b/img/gray-dither.gif new file mode 100644 index 0000000..8809b7a Binary files /dev/null and b/img/gray-dither.gif differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..8f42f4f --- /dev/null +++ b/index.html @@ -0,0 +1,166 @@ + + + + +asciiblaster + + + + + + + +
+
+ +
+
+
+ +
+
+
+
+ . + +
+
+
+
+
+
+
+
+ x fg
+ x bg
+ x char
+
+ + add + _ mirror x
+ _ mirror y
+
+
+
+
+
+ square
+ circle
+ cross
+ text
+ fill
+ select
+
+ rotate
+ scale
+ translate
+ slice
+ + _ grid + + x vertical + +
+ +
+
+ new + save + load +
+ shader + webcam + doc + gallery +
+ _ advanced +
+ > send to IRC +
+
+
+ no
+ paint
+
+
+
+ brush: 5 x 5
+ canvas: 100 x 30
+
+ +
+ ascii *irssi mirc ansi + + + +
+
colorcode is too wide for irc and is cutoff
+ +
+ +
+ _ animate + to *canvas brush selection +
+ +
+
+
+
+ x + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..6afbbcc --- /dev/null +++ b/js/app.js @@ -0,0 +1,100 @@ + +var dragging = false +var drawing = false +var erasing = false +var selecting = false +var filling = false +var changed = false +var transforming = false +var mirror_x = false +var mirror_y = false +var focused + +var canvas, tools, palette, controls, brush, mode +var current_tool, current_filetool, current_canvas +var mouse = { x: 0, y: 0 } + +function init () { + build() + bind() + clipboard.load_from_location() +} +function build () { + shader.init() +// shader.run(canvas) + shader.animate() + + canvas.append(canvas_rapper) + brush.append(brush_rapper) + palette.append(palette_rapper) + letters.append(letters_rapper) + letters.repaint("Basic Latin") + + controls.circle.focus() +// controls.shader.focus() + + brush.bg = colors.red + brush.generate() + brush.build() + + // controls.grid.use() + canvas.resize_rapper() +} +function bind () { + canvas.bind() + palette.bind() + letters.bind() + brush.bind() + controls.bind() + keys.bind() + clipboard.bind() + + window.addEventListener('mouseup', function(e){ + dragging = erasing = false + // if (current_filetool.name != 'shader' && current_filetool.name != 'load' && current_filetool.name != 'save' && is_desktop) { + // cursor_input.focus() + // } + + var ae = document.activeElement + + if (ae !== shader_textarea && ae !== import_textarea && ae !== username_input && ae !== upload_input) { + if (is_desktop) cursor_input.focus() + } + + if (selecting) { + selection.up(e) + } + else if (transforming) { + transform.up(e) + } + }) + window.addEventListener("touchend", function(){ + if (current_tool.name === "text") { + if (is_desktop) cursor_input.focus() + } + dragging = false + }) + + window.addEventListener('mousedown', function(e){ + // if (current_filetool.name != 'shader' && is_desktop) { cursor_input.focus() } + }) + + document.addEventListener('DOMContentLoaded', function(){ + if (is_desktop) { cursor_input.focus() } + document.body.classList.remove('loading') + }) + + window.onbeforeunload = function() { + // if (changed && !in_iframe()) return "You have edited this drawing." + } + + function in_iframe () { + try { + return window.self !== window.top; + } catch (e) { + return true; + } + } +} + +init() diff --git a/js/blit.js b/js/blit.js new file mode 100644 index 0000000..2dce3e5 --- /dev/null +++ b/js/blit.js @@ -0,0 +1,105 @@ +var blit = (function(){ + var blit = {} + blit.and = blit.atop = function(A, B, x, y){ + x = x || 0 ; y = y || 0 + B.forEach(function(lex, u, v){ + var cell = A.getCell(u+x, v+y) + if (cell && lex.opacity > 0) { + cell.assign(lex) + } + }) + } + blit.or = blit.under = function(A, B, x, y){ + x = x || 0 ; y = y || 0 + B.forEach(function(lex, u, v){ + var cell = A.getCell(u+x, v+y) + if (cell && cell.opacity == 0) { + cell.assign(lex) + } + }) + } + // copy the region of A beginning at x,y into B + blit.copy_from = function(A, B, x, y){ + x = x || 0 ; y = y || 0 + B.forEach(function(lex, u, v){ + var cell = A.getCell(u+x, v+y) + if (cell) { + lex.assign(cell) + } + }) + } + blit.copy_toroidal_from = function(A, B, x, y){ + x = x || 0 ; y = y || 0 + B.forEach(function(lex, u, v){ + var cell = A.get(u+x, v+y) + if (cell) { + lex.assign(cell) + } + }) + } + blit.copy_to = function(A, B, x, y){ + x = x || 0 ; y = y || 0 + B.forEach(function(lex, u, v){ + var cell = A.getCell(u+x, v+y) + if (cell) { + cell.assign(lex) + } + }) + } + blit.invert = function(A, B, x, y){ + x = x || 0 ; y = y || 0 + B.forEach(function(lex, u, v){ + var cell = A.getCell(u+x, v+y) + if (cell && lex.opacity > 0) { + cell.fg = get_inverse(cell.fg) + cell.bg = get_inverse(cell.bg) + } + }) + } + var distance_rect = function(x, y, ratio){ + return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x, 2)) + } + var distance_square = function(x, y, ratio){ + return Math.sqrt((Math.pow(y * ratio, 2)) + Math.pow(x * ratio, 2)) + } + blit.circle = function(A, lex){ + var hw = brush.w/2, hh = brush.h/2 + var ratio, distance + + if (brush.w === brush.h){ + distance = distance_square + ratio = hw / hh * (brush.w === 3 || brush.w === 5 ? 1.2 : 1.05) + } else { + distance = distance_rect + ratio = hw / hh + } + + A.forEach(function(lex,x,y) { + if (distance(x - hw + 0.5, y - hh + 0.5, ratio) > hw){ + lex.clear() + } + }) + } + blit.cross = function(A, lex){ + A.forEach(function(lex,x,y) { + if ((x+y)%2) { + lex.clear() + } + }) + } + blit.inverted_cross = function(A, lex){ + // 1x1 brush should still draw something + if (A.w == 1 && A.h == 1) { + return + } + A.forEach(function(lex,x,y) { + if (!((x+y)%2)) { + lex.clear() + } + }) + } + blit.square = function(A, lex){ + // i.e. no transparency + } + return blit +})() diff --git a/js/clipboard.js b/js/clipboard.js new file mode 100644 index 0000000..f636af9 --- /dev/null +++ b/js/clipboard.js @@ -0,0 +1,330 @@ +var clipboard = (function () { + + var exports = { + format: "irssi", + importing: false, + visible: false, + canvas: document.createElement("canvas"), + canvas_r: document.createElement("canvas"), + + bind: function () { +// import_ascii.addEventListener("change", exports.setFormat("ascii")) +// import_irssi.addEventListener("change", exports.setFormat("irssi")) +// import_mirc.addEventListener("change", exports.setFormat("mirc")) + import_button.addEventListener("click", exports.import_colorcode) + export_button.addEventListener("click", exports.export_data) + save_button.addEventListener("click", exports.save_png) + upload_button.addEventListener("click", exports.upload_png) + import_textarea.addEventListener("focus", exports.focus) + import_textarea.addEventListener("blur", exports.blur) + import_textarea.addEventListener('paste', exports.paste) +// import_irssi.setAttribute("checked", true) + }, + setFormat: function (name) { + return function () { + clipboard.format = name + if (! clipboard.importing) { clipboard.export_data() } + } + }, + show: function () { import_rapper.style.display = "block"; clipboard.visible = true; changed = false }, + hide: function () { import_rapper.style.display = "none"; clipboard.visible = false }, + focus: function () { + if (! clipboard.importing) { + import_textarea.focus() + import_textarea.select() + } + }, + blur: function () { + }, + + import_mode: function () { + focus() + clipboard.importing = true + gallery_rapper.style.display = 'none' + format_el.style.display = 'none' + cutoff_warning_el.style.display = 'none' + import_buttons.style.display = "inline" + import_textarea.value = "" + }, + export_mode: function () { + focus() + clipboard.importing = false + import_buttons.style.display = "none" + format_el.style.display = 'inline' + cutoff_warning_el.style.display = 'none' + gallery_rapper.style.display = 'inline' + clipboard.export_data() + }, + + paste: function (e) { + e.preventDefault() + // images will come through as files + var types = toArray(e.clipboardData.types) + import_textarea.value = "" + types.forEach(function(type, i){ + console.log(type) + // this can be text/plain or text/html.. + if (type.match('text/plain')) { + import_textarea.value = e.clipboardData.getData(type) + } + else { + console.error("unknown type!", item.type) + } + }) + }, + + import_colorcode: function (data, no_undo) { + if (data && data.preventDefault) { + data = import_textarea.value + } + else { + data = data || import_textarea.value + } + + var irssi_style_regex = /^\s*\/exec -out printf ("%b" )?"/; + + // turn irssi style into mirc style + if (data.match(irssi_style_regex)){ + data = data.replace(/\\x03/gm, '\x03') + .replace(/(\\x..)+/gm, unicode.unescapeFromEscapedBytes) + .replace(/\\x5C/g, '\\') + .replace(/\\n/gm, '\n') + .replace(/\\`/gm, '`') + .replace(/\\"/gm, '"') + .replace(/\\\$/gm, '$') + .replace(irssi_style_regex, '') + .replace(/"\s*$/, '') + } + + // not a colorcode + if (!data.match(/\x03/)) + return exports.import_text(); + + var json = colorcode.to_json(data, {fg:0, bg:1}) + + if (!no_undo) undo.new() + if (!no_undo) undo.save_rect(0,0, canvas.w, canvas.h) + if (json.w !== canvas.w || json.h !== canvas.h){ + if (!no_undo) undo.save_size(canvas.w, canvas.h) + canvas.resize(json.w, json.h, true) + } + canvas.clear() + + for (var y = 0, line; line = json.lines[y]; y++){ + var row = canvas.aa[y] + for (var x = 0, char; char = line[x]; x++){ + var lex = row[x] + lex.char = String.fromCharCode(char.value) + lex.fg = char.fg + lex.bg = char.bg + lex.opacity = 1 + lex.build() + } + } + + current_filetool && current_filetool.blur() + }, + + import_text: function () { + var data = import_textarea.value + var lines = data.split("\n") + var width = lines.reduce(function(a,b){ console.log(a,b); return Math.max(a, b.length) }, 0) + var height = lines.length + if (width > canvas.max) { + return alert("input too wide") + } + if (height > canvas.max) { + return alert("input too tall") + } + undo.new() + undo.save_rect(0,0, canvas.w, canvas.h) + canvas.clear() + lines.forEach(function(line, y){ + var row = canvas.aa[y] + if (! row) return + for (var x = 0; x < line.length; x++) { + var lex = row[x] + if (! lex) return + lex.char = line[x] + lex.fg = brush.bg + lex.opacity = 1 + lex.build() + } + }) + // TODO: some notion of a "selected" region which cuts/clones the underlying region + +// var pasted_region = new Matrix (width, height, function(x,y){ +// var lex = new Lex (x,y) +// lex.char = lines[y][x] || " " +// lex.build() +// return lex +// }) + }, + export_data: function () { + var output + // switch (clipboard.format) { + switch (controls.save_format.value) { + case 'ascii': + output = canvas.ascii() + break + case 'mirc': + output = canvas.mirc({cutoff: 400}) + break + case 'irssi': + output = canvas.irssi({cutoff: 400}) + break + case 'ansi': + output = canvas.ansi() + break + } + if (output.cutoff){ + cutoff_warning_el.style.display = 'block' + } else { + cutoff_warning_el.style.display = 'none' + } + import_textarea.value = output + clipboard.focus() + return output + }, + + rotate_canvas: function(){ + var cr = clipboard.canvas_r, c = clipboard.canvas + cr.width = c.height + cr.height = c.width + var ctx = cr.getContext('2d') + ctx.resetTransform() + ctx.translate(0, cr.height) + ctx.rotate(-Math.PI / 2) + ctx.drawImage(c, 0, 0) + return cr + }, + + export_canvas: function (done_fn) { + var opts = { + palette: 'mirc', + font: canvas.pixels ? 'fixedsys_8x8' : 'fixedsys_8x15', + fg: 0, + bg: 1, + canvas: clipboard.canvas + } + opts.done = function(){ + var c = canvas.rotated ? clipboard.rotate_canvas() : clipboard.canvas + if (done_fn) done_fn(c) + } + + var start = Date.now(); + colorcode.to_canvas(canvas.mirc(), opts) + var total = Date.now() - start; + console.log("took " + total) + }, + + filename: function () { + return [ +new Date, "ascii", user.username ].join("-") + }, + + save_png: function () { + var save_fn = function(canvas_out){ + var filename = clipboard.filename() + ".png" + var blob = PNG.canvas_to_blob_with_colorcode(canvas_out, canvas.mirc()) + saveAs(blob, filename); + } + clipboard.export_canvas(save_fn) + }, + + upload_png: function () { + var upload_fn = function(canvas_out){ + var blob = PNG.canvas_to_blob_with_colorcode(canvas_out, canvas.mirc()) + var filename = clipboard.filename() + var tag = 'ascii' + upload(blob, filename, tag, canvas.mirc()) + } + clipboard.export_canvas(upload_fn) + } + + } + + // http...?a=1&b=2&b=3 -> {a: '1', b: ['2', '3']} + function parse_url_search_params(url){ + var params = {} + url = url.split('?') + if (url.length < 2) return params + + var search = url[1].split('&') + for (var i = 0, pair; pair = search[i]; i++){ + pair = pair.split('=') + if (pair.length < 2) continue + var key = pair[0] + var val = pair[1] + if (key in params){ + if (typeof params[key] === 'string'){ + params[key] = [params[key], val] + } + else params[key].push(val) + } + else params[key] = val + } + return params + } + + function get_filetype(txt){ + txt = txt.split('.') + return txt[txt.length - 1].toLowerCase() + } + + function fetch_url(url, f, type){ + type = type || 'arraybuffer' + url = "/cgi-bin/proxy?" + url + //url = "http://198.199.72.134/cors/" + url + var xhr = new XMLHttpRequest() + xhr.open('GET', url, true) + xhr.responseType = type + xhr.addEventListener('load', function(){ f(xhr.response) }) + xhr.send() + } + + function load_text(txt){ + clipboard.import_colorcode(txt, true) + } + + function load_png(buf){ + var chunks = PNG.decode(buf) + if (!chunks) return + var itxt_chunks = [] + for (var i=0, c; c=chunks[i]; i++){ + if (c.type !== 'iTXt') continue + var itxt = PNG.decode_itxt_chunk(c) + if (!itxt.keyword || itxt.keyword !== 'colorcode') continue + clipboard.import_colorcode(itxt.data, true) + } + } + + function sally_url_convert(url){ + var png_regex = /^https?:\/\/jollo\.org\/den\/sallies\/([0-9]+)\/([^.]+)\.png$/ + var matches = url.match(png_regex) + if (!matches) return url + return 'http://jollo.org/den/sallies/' + matches[1] + '/raw-' + matches[2] + '?.txt' + // txt suffix to force asdf proxy + } + + exports.load_from_location = function(){ + var params = parse_url_search_params(window.location + '') + if (!params.url) return + var url = params.url + url = sally_url_convert(url) + var type = get_filetype(url) + switch (type){ + case 'txt': + fetch_url(url, load_text, 'text') + break + case 'png': + fetch_url(url, load_png) + break + } + + } + + return exports + +})() + + diff --git a/js/color.js b/js/color.js new file mode 100644 index 0000000..c518327 --- /dev/null +++ b/js/color.js @@ -0,0 +1,106 @@ + +var fillColor = 1 // black + +var color_names = ("white black dark-blue green red dark-red purple orange " + + "yellow lime teal cyan blue magenta dark-gray light-gray").split(" "); + +var all_color_hue_order = "dark-red red orange yellow lime green teal cyan blue dark-blue purple magenta black dark-gray light-gray white".split(" "); +var all_color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green white light-gray dark-gray black".split(" "); +var color_hue_order = "dark-red red orange yellow lime cyan teal blue dark-blue purple magenta".split(" "); +var color_inv_order = "cyan teal blue dark-blue purple magenta dark-red red orange yellow lime green".split(" "); +var gray_names = ("black dark-gray light-gray white").split(" ") + +var fire_names = ("black dark-red red orange yellow white cyan").split(" ") +var red_names = ("black dark-red red").split(" ") +var yellow_names = ("black orange yellow white").split(" ") +var green_names = ("teal green lime").split(" ") +var blue_names = ("black dark-blue blue").split(" ") +var purple_names = ("dark-blue purple magenta red").split(" ") +var dark_gray_names = ("black dark-blue teal dark-gray light-gray white").split(" ") +var color_alphabet = "abcdefghijklmnop"; +var colors = {} +color_names.forEach(function(name, i){ + colors[name.replace("-", "")] = i + colors[name] = i +}) +colors.brown = 5 + +function get_inverse (n) { return colors[all_color_inv_order.indexOf(color_names[n])] } + +function mirc_color (n) { return mod(n, 16)|0 } +function mirc_color_reverse (n) { return mod(-(n+1), 16)|0 } +function all_hue (n) { return colors[all_color_hue_order[mod(n, 16)|0]] } +function all_inv_hue (n) { return colors[all_color_inv_order[mod(n, 16)|0]] } +function hue (n) { return colors[color_hue_order[mod(n, 11)|0]] } +function rand_hue () { return colors[color_hue_order[randint(11)]] } +function rand_gray () { return colors[gray_names[randint(4)]] } +function inv_hue (n) { return colors[color_inv_order[mod(n, 11)|0]] } +function gray (n) { return colors[gray_names[mod(n, 4)|0]] } +function fire (n) { return colors[fire_names[mod(n, 7)|0]] } +function red (n) { return colors[red_names[mod(n, 3)|0]] } +function yellow (n) { return colors[yellow_names[mod(n, 4)|0]] } +function green (n) { return colors[green_names[mod(n, 3)|0]] } +function blue (n) { return colors[blue_names[mod(n, 3)|0]] } +function purple (n) { return colors[purple_names[mod(n, 4)|0]] } +function dark_gray (n) { return colors[dark_gray_names[mod(n, 4)|0]] } + +var css_lookup = { + 'rgb(255, 255, 255)': 'A', + 'rgb(0, 0, 0)': 'B', + 'rgb(0, 0, 127)': 'C', + 'rgb(0, 147, 0)': 'D', + 'red': 'E', + 'rgb(127, 0, 0)': 'F', + 'rgb(156, 0, 156)': 'G', + 'rgb(252, 127, 0)': 'H', + 'rgb(255, 255, 0)': 'I', + 'rgb(0, 252, 0)': 'J', + 'rgb(0, 147, 147)': 'K', + 'rgb(0, 255, 255)': 'L', + 'rgb(0, 0, 252)': 'M', + 'rgb(255, 0, 255)': 'N', + 'rgb(127, 127, 127)': 'O', + 'rgb(210, 210, 210)': 'P', +}; +var css_reverse_lookup = {} +Object.keys(css_lookup).forEach(function(color){ + css_reverse_lookup[ css_lookup[color].charCodeAt(0) - 65 ] = color +}) + +var ansi_fg = [ + 97, // white + 30, // black + 34, // dark blue + 32, // green + 91, // light red + 31, // dark red + 35, // purple + 33, // "dark yellow" (orange?) + 93, // "light yellow" + 92, // light green + 36, // cyan (teal?) + 96, // light cyan + 94, // light blue + 95, // light magenta + 90, // dark gray + 37, // light gray +] + +var ansi_bg = [ + 107, // white + 40, // black + 44, // dark blue + 42, // green + 101, // light red + 41, // dark red + 45, // purple + 43, // yellow (orange) + 103, // light yellow + 102, // light green + 46, // cyan (teal?) + 106, // light cyan + 104, // light blue + 105, // light magenta + 100, // dark gray + 47, // light gray +] diff --git a/js/dither.js b/js/dither.js new file mode 100644 index 0000000..f5c86c9 --- /dev/null +++ b/js/dither.js @@ -0,0 +1,10 @@ +var dither = { + aa: '▓▒░ ', + a: '▓', + b: '▒', + c: '░', + d: ' ', + p: function(n){ + return dither.aa[Math.floor(Math.abs(n) % 4)] + } +} diff --git a/js/draw.js b/js/draw.js new file mode 100644 index 0000000..9b0e3b9 --- /dev/null +++ b/js/draw.js @@ -0,0 +1,221 @@ + +var draw = (function(){ + + var last_point = [0,0] + + function down (e, lex, point) { + var w = canvas.w, h = canvas.h + erasing = (e.which == "3" || e.ctrlKey) + changed = true + if (e.shiftKey) { + line (lex, last_point, point, erasing) + if (mirror_x) { + line(lex, [w-last_point[0], last_point[1]], [w-point[0], point[1]], erasing) + } + if (mirror_y) { + line(lex, [last_point[0], h-last_point[1]], [point[0], h-point[1]], erasing) + } + if (mirror_x && mirror_y) { + line(lex, [w-last_point[0], h-last_point[1]], [w-point[0], h-point[1]], erasing) + } + } + else { + stamp (canvas, brush, point[0], point[1], erasing) + if (mirror_x) { + stamp (canvas, brush, w-point[0], point[1], erasing) + } + if (mirror_y) { + stamp (canvas, brush, point[0], h-point[1], erasing) + } + if (mirror_x && mirror_y) { + stamp (canvas, brush, w-point[0], h-point[1], erasing) + } + } + last_point[0] = point[0] + last_point[1] = point[1] + } + + function set_last_point (e, point) { + last_point[0] = point[0] + last_point[1] = point[1] + } + + function move (e, lex, point) { + var w = canvas.w, h = canvas.h + line(lex, last_point, point, erasing) + if (mirror_x) { + line(lex, [w-last_point[0], last_point[1]], [w-point[0], point[1]], erasing) + } + if (mirror_y) { + line(lex, [last_point[0], h-last_point[1]], [point[0], h-point[1]], erasing) + } + if (mirror_x && mirror_y) { + line(lex, [w-last_point[0], h-last_point[1]], [w-point[0], h-point[1]], erasing) + } + + last_point[0] = point[0] + last_point[1] = point[1] + } + + function move_toroidal (e, lex, point) { + var w = canvas.w, h = canvas.h + var src_x_quantile = quantile( last_point[0], w ) + var src_y_quantile = quantile( last_point[1], h ) + var dst_x_quantile = quantile( point[0], w ) + var dst_y_quantile = quantile( point[1], h ) + var src_x_mod = mod( last_point[0], w ) + var src_y_mod = mod( last_point[1], h ) + var dst_x_mod = mod( point[0], w ) + var dst_y_mod = mod( point[1], h ) + // if we've moved across the edge of the board, draw two lines + if (src_x_quantile != dst_x_quantile || src_y_quantile != dst_y_quantile) { + var xa, ya + if (src_x_quantile < dst_x_quantile) { + xa = [ + [src_x_mod, dst_x_mod + w], + [src_x_mod-w, dst_x_mod], + ] + } + else if (src_x_quantile == dst_x_quantile) { + xa = [ + [src_x_mod, dst_x_mod], + [src_x_mod, dst_x_mod], + ] + } + else { + xa = [ + [src_x_mod, dst_x_mod-w], + [src_x_mod+w, dst_x_mod], + ] + } + + if (src_y_quantile < dst_y_quantile) { + ya = [ + [src_y_mod, dst_y_mod + h], + [src_y_mod-h, dst_y_mod], + ] + } + else if (src_y_quantile == dst_y_quantile) { + ya = [ + [src_y_mod, dst_y_mod], + [src_y_mod, dst_y_mod], + ] + } + else { + ya = [ + [src_y_mod, dst_y_mod-h], + [src_y_mod+h, dst_y_mod], + ] + } + line(lex, [ xa[0][0], ya[0][0] ], [ xa[0][1], ya[0][1] ], erasing) + line(lex, [ xa[1][0], ya[1][0] ], [ xa[1][1], ya[1][1] ], erasing) + } + else { + var x_a = mod( last_point[0], w ) + var y_a = mod( last_point[1], h ) + var x_b = mod( point[0], w ) + var y_b = mod( point[1], h ) + var last_point_mod = [x_b, y_b], point_mod = [x_a, y_a] + line(lex, last_point_mod, point_mod, erasing) + // if (mirror_x) { + // line(lex, [w-last_point_mod[0], last_point_mod[1]], [w-point_mod[0], point_mod[1]], erasing) + // } + // if (mirror_y) { + // line(lex, [last_point_mod[0], h-last_point_mod[1]], [point_mod[0], h-point_mod[1]], erasing) + // } + } + last_point[0] = point[0] + last_point[1] = point[1] + // y = point.y + } + + function point (lex, x, y, erasing) { + stamp (canvas, brush, x, y, erasing) + } + + function line (lex, a, b, erasing) { + var len = dist(a[0], a[1], b[0], b[1]) + var bw = 1 + var x, y, i; + for (var i = 0; i <= len; i += bw) { + x = lerp(i / len, a[0], b[0]) + y = lerp(i / len, a[1], b[1]) + stamp (canvas, brush, x, y, erasing) + } + } + + function stamp (canvas, brush, x, y, erasing) { + var hh = brush.w/2|0 + brush.forEach(function(lex, s, t){ + s = round( s + x-hh ) + t = round( t + y-hh ) + if (s >= 0 && s < canvas.w && t >= 0 && t < canvas.h) { + if (lex.opacity === 0 && lex.char === ' ') return; + var aa = canvas.aa[t][s] + undo.save_lex(s, t, aa) + if (erasing) { + aa.erase(lex) + } + else { + aa.stamp(lex, brush) + } + } + }) + } + + function fill (lex, x, y) { + var q = [ [x,y] ] + var aa = canvas.aa + var target = aa[y][x].clone() + var n, w = 0, e = 0, j = 0 + var kk = 0 + // gets into a weird infinite loop if we don't break here.. :\ + if (target.eq(lex)) { return } + LOOP: while (q.length) { + n = q.shift() + if (aa[n[1]][n[0]].ne(target)) { + continue LOOP + } + w = e = n[0] + j = n[1] + WEST: while (w > 0) { + if (aa[j][w-1].eq(target)) { + w = w-1 + } + else { + break WEST + } + } + EAST: while (e < canvas.w-1) { + if (aa[j][e+1].eq(target)) { + e = e+1 + } + else { + break EAST + } + } + for (var i = w; i <= e; i++) { + undo.save_lex(i, j, aa[j][i]) + aa[j][i].assign(lex) + if (j > 0 && aa[j-1][i].eq(target)) { + q.push([ i, j-1 ]) + } + if (j < canvas.h-1 && aa[j+1][i].eq(target)) { + q.push([ i, j+1 ]) + } + } + } + } + + var draw = {} + draw.down = down + draw.set_last_point = set_last_point + draw.move = move + draw.move_toroidal = move_toroidal + draw.stamp = stamp + draw.line = line + draw.point = point + draw.fill = fill + return draw + +})() diff --git a/js/lex.js b/js/lex.js new file mode 100644 index 0000000..b2f54dc --- /dev/null +++ b/js/lex.js @@ -0,0 +1,138 @@ +function Lex (x,y) { + if (typeof x == "number") { + this.y = y + this.x = x + this.span = document.createElement("span") + } + else { + this.span = x + } + this.fg = colors.white + this.bg = colors.black + this.char = " " + this.opacity = 1 + this.focused = false +} +Lex.prototype.build = function(){ + if (isNaN(this.bg) || this.bg == Infinity || this.bg == -Infinity) this.bg = colors.black + if (isNaN(this.fg) || this.fg == Infinity || this.fg == -Infinity) this.fg = colors.black + this.span.className = this.css() + this.span.innerHTML = this.html() +} +Lex.prototype.css = function(){ + return ( + this.focused ? + "focused " : "" + ) + ( + this.opacity === 0 ? + "transparent f" + color_alphabet[modi(this.fg,16)] : + "f" + color_alphabet[modi(this.fg,16)] + " b" + color_alphabet[modi(this.bg,16)] + ) +} +Lex.prototype.html = function(){ + return this.char == " " ? " " : this.char || " " +} +Lex.prototype.read = function(){ + this.char = this.span.innerHTML + return this.char +} +Lex.prototype.ascii = function(){ + return this.char || " " +} +Lex.prototype.sanitize = function(){ + switch (this.char) { +// case "%": return "%" + case undefined: + case "": return " " + default: return this.char + } +} +var fgOnly = false +Lex.prototype.mirc = function(){ + var char = this.char || " " + if (fgOnly) { + return "\x03" + (this.fg&15) + char + } + if ((this.bg&15) < 10 && ! isNaN(parseInt(char))) { + return "\x03" + (this.fg&15) + ",0" + (this.bg&15) + char + } + else { + return "\x03" + (this.fg&15) + "," + (this.bg&15) + char + } +} +Lex.prototype.ansi = function(){ + var fg = ansi_fg[ this.fg&15 ] + var bg = ansi_bg[ this.bg&15 ] + var c = this.sanitize() + if (c == "\\") c = "\\\\" + if (c == '"') c = '\\"' + return "\\e[" + fg + ";" + bg + "m" + c +} +Lex.prototype.assign = function (lex){ + this.fg = lex.fg + this.bg = lex.bg + this.char = lex.char + this.opacity = lex.opacity + this.build() +} +Lex.prototype.stamp = function (lex, brush){ + if (brush.draw_fg) this.fg = lex.fg + if (brush.draw_bg && lex.opacity > 0) this.bg = lex.bg + if (brush.draw_char) this.char = lex.char + this.opacity = 1 + this.build() +} +Lex.prototype.clone = function () { + var lex = new Lex (0,0) + lex.assign(this) + return lex +} +Lex.prototype.erase = function (){ + this.fg = fillColor + this.bg = fillColor + this.char = " " + this.opacity = 1 + this.build() +} +Lex.prototype.eq = function(lex){ + return lex && this.fg == lex.fg && this.bg == lex.bg && this.char == lex.char +} +Lex.prototype.eqColor = function(lex){ + return lex && this.fg == lex.fg && this.bg == lex.bg +} +Lex.prototype.ne = function(lex){ + return ! this.eq(lex) +} +Lex.prototype.clear = function(){ + this.bg = colors.black + this.fg = 0 + this.char = " " + this.opacity = 0 + this.build() +} +Lex.prototype.isClear = function(){ + return this.bg == 1 && this.fg == 0 && this.char == " " +} +Lex.prototype.focus = function(){ + if (focused) focused.blur() + this.span.classList.add('focused') + this.focused = true + focused = this +} +Lex.prototype.blur = function(){ + focused = null + this.span && this.span.classList.remove('focused') + this.focused = false + this.onBlur && this.onBlur() +} +Lex.prototype.demolish = function(){ + if (this.span.parentNode) { this.span.parentNode.removeChild(this.span) } + this.span = null +} +Lex.prototype.key = function(char, keyCode) { + if (! char) { return } + this.char = char + this.fg = brush.fg + this.build() + return true +} diff --git a/js/matrix.js b/js/matrix.js new file mode 100644 index 0000000..dbc76c7 --- /dev/null +++ b/js/matrix.js @@ -0,0 +1,321 @@ +function Matrix (w,h,f){ + this.x = 0 + this.y = 0 + this.w = w + this.h = h + this.f = f + this.focus_x = 0 + this.focus_y = 0 + this.initialize() +} +Matrix.prototype.initialize = function(f){ + var w = this.w || 1, h = this.h || 1, f = f || this.f + var aa = new Array (h) + for (var y = 0; y < h; y++) { + aa[y] = new Array (w) + for (var x = 0; x < w; x++) { + aa[y][x] = f(x,y) + } + } + this.aa = aa +} +Matrix.prototype.rebuild = function (){ + this.demolish() + this.initialize() + this.append() + this.bind() + this.generate && this.generate() + this.focus_clamp() + check_if_lost_focus() +} +Matrix.prototype.clone = function () { + var base = this + var clone = new Matrix(this.w, this.h, function(x,y){ + return base.getCell(x,y).clone() + }) + clone.f = this.f + return clone +} +Matrix.prototype.assign = function (mat) { + var base = this + this.demolish() + this.w = mat.w + this.h = mat.h +// this.f = function(){} + this.initialize(function(x,y){ + var el = mat.getCell(x,y).clone() + el.build() + return el + }) + this.append() + this.bind() + check_if_lost_focus() + return this +} + +Matrix.prototype.bind = function () {} +Matrix.prototype.demolish = function (){ + this.forEach(function(lex){ + lex.demolish() + }) + while (this.rapper && this.rapper.firstChild) { + this.rapper.removeChild(this.rapper.firstChild); + } + this.aa.forEach(function(row){ + row.length = 0 + }) + this.aa.length = 0 +} +Matrix.prototype.forEach = function(f){ + this.aa.forEach(function(row, y){ + row.forEach(function(lex, x){ + f(lex, x, y) + }) + }) +} +Matrix.prototype.focus_clamp = function(){ + this.focus_x = clamp(this.focus_x, 0, this.w - 1) + this.focus_y = clamp(this.focus_y, 0, this.h - 1) +} +Matrix.prototype.focus_add = function(x, y){ + this.focus(this.focus_x + x, this.focus_y + y) +} +Matrix.prototype.focus = function(x, y){ + if (x === undefined) x = this.focus_x + if (y === undefined) y = this.focus_y + x = mod(x, this.w) + y = mod(y, this.h) + this.focus_x = x + this.focus_y = y + + //focused_input = this + this.aa[y][x].focus() +} +Matrix.prototype.focusLex = function(y,x){ + if (x < 0) { + y -= 1 + } + if (x > this.aa[0].length) { + y += 1 + } + this.aa[mod(y,this.h)][mod(x,this.w)].focus() +} +Matrix.prototype.clear = function(){ + this.forEach(function(lex,x,y){ lex.clear() }) +} +Matrix.prototype.erase = function(){ + this.forEach(function(lex,x,y){ lex.erase() }) +} +Matrix.prototype.fill = function(lex){ + this.fg = lex.fg + this.bg = lex.bg + this.char = lex.char + this.opacity = lex.opacity + this.forEach(function(el,x,y){ + el.assign(lex) + el.build() + }) +} + +Matrix.prototype.build = function(){ + this.forEach(function(lex,x,y){ + lex.build() + }) +} +Matrix.prototype.append = function(rapper){ + rapper = this.rapper = rapper || this.rapper + if (! this.rapper) return + this.aa.forEach(function(row, y){ + var div = document.createElement("div") + row.forEach(function(lex, x) { + div.appendChild(lex.span) + }) + rapper.appendChild( div ) + }) +} +Matrix.prototype.region = function(w,h,x,y) { + w = w || 1 + h = h || 1 + x = x || 0 + y = y || 0 + var parent = this + var mat = new Matrix(w, h, function(x,y){ + return parent.aa[y][x] + }) + mat.f = this.f + return mat +} +Matrix.prototype.setCell = function(lex,x,y){ + this.aa[y] && this.aa[y][x] && this.aa[y][x].assign(lex) +} +Matrix.prototype.getCell = function(x,y){ + if (this.aa[y] && this.aa[y][x]) return this.aa[y][x] + else return null +} +Matrix.prototype.get = function(x,y){ + y = floor(mod(y || 0, this.h)) + x = floor(mod(x || 0, this.w)) + if (this.aa[y] && this.aa[y][x]) return this.aa[y][x] + else return null +} + +Matrix.prototype.resize = function(w,h){ + w = w || canvas.w + h = h || canvas.h + var div, row, lex + var f = this.f, old_h = this.aa.length, old_w = this.aa[0].length + var rapper = this.rapper + w = max(w, 1) + h = max(h, 1) + if (h < old_h) { + for (var y = old_h; y > h; y--) { + row = this.aa.pop() + div = row[0].span.parentNode + row.forEach(function(lex, x){ + lex.demolish() + }) + div.parentNode.removeChild(div) + } + } + else if (h > old_h) { + for (var y = old_h; y < h; y++) { + div = document.createElement("div") + rapper.appendChild( div ) + this.aa[y] = new Array (w) + for (var x = 0; x < w; x++) { + lex = this.aa[y][x] = f(x,y) + div.appendChild(lex.span) + } + } + } + + if (w < old_w) { + this.aa.forEach(function(row, y){ + while (row.length > w) { + lex = row.pop() + lex.demolish() + } + }) + } + else if (w > old_w) { + this.aa.forEach(function(row, y){ + div = row[0].span.parentNode + for (var x = row.length; x < w; x++) { + lex = row[x] = f(x,y) + div.appendChild(lex.span) + } + }) + } + + this.w = w + this.h = h + this.bind && this.bind() + this.focus_clamp() + if (this.rapper && this.rapper.parentNode != document.body) { + this.resize_rapper() + } +} +Matrix.prototype.resize_rapper = function(){ + var cell = canvas.aa[0][0].span + var cw = cell.offsetWidth + var ch = cell.offsetHeight +// if (canvas.grid) { ch++ } + var width = cw * this.aa[0].length + var height = ch * this.aa.length + if (canvas.grid) { width++; height++ } + if (this.rotated) { + this.rapper.parentNode.classList.add("rotated") + this.rapper.parentNode.style.height = (width) + "px" + this.rapper.parentNode.style.width = (height) + "px" + this.rapper.style.top = (width/2) + "px" + // this.rapper.style.left = ((canvas_rapper.offsetHeight+20)/2) + "px" + } + else { + this.rapper.parentNode.classList.remove("rotated") + this.rapper.parentNode.style.height = "" + this.rapper.style.width = + this.rapper.parentNode.style.width = (width) + "px" + this.rapper.style.top = "" + // canvas_rapper.style.left = "auto" + } +} +Matrix.prototype.ascii = function () { + var lines = this.aa.map(function(row, y){ + var last, line = "" + row.forEach(function(lex, x) { + line += lex.ascii() + }) + return line // .replace(/\s+$/,"") + }) + var txt = lines.join("\n") + return txt +} +Matrix.prototype.ansi = function (opts) { + var lines = this.aa.map(function(row, y){ + var last, line = "" + row.forEach(function(lex, x) { + if (lex.eqColor(last)) { + line += lex.sanitize() + } + else { + line += lex.ansi() + last = lex + } + }) + return line + }) + var txt = lines.filter(function(line){ return line.length > 0 }).join('\\e[0m\\n') + "\\e[0m" + return 'echo -e "' + txt + '"' +} +Matrix.prototype.mirc = function (opts) { + var cutoff = false + var lines = this.aa.map(function(row, y){ + var last, line = "" + row.forEach(function(lex, x) { + if (lex.eqColor(last)) { + line += lex.sanitize() + } + else { + line += lex.mirc() + last = lex + } + }) + if (opts && opts.cutoff && line.length > opts.cutoff) { + cutoff = true + return line.substr(0, opts.cutoff) + } + return line + }) + + var txt = lines.filter(function(line){ return line.length > 0 }).join('\n') + + if (cutoff) { + txt = new String(txt) + txt.cutoff = true + } + return txt +} +Matrix.prototype.irssi = function(opts){ + var mirc = this.mirc(opts) + var txt = mirc + // .replace(/\%/g, '%%') + .replace(/\\/g, '\\x5C') + .replace(/\"/g, '\\\"') + // .replace(/\'/g, '\\\'') + .replace(/\`/g, '\\\`') + .replace(/\$/g, '\\$') + // .replace(/\n\s+/g, '\n') + // .replace(/\s+$/g, '\n') + // .replace(/^\n+/, '') + .replace(/\n/g, '\\n') + .replace(/\x02/g, '\\x02') + .replace(/\x03/g, '\\x03') + + txt = unicode.escapeToEscapedBytes(txt) + txt = '/exec -out printf "%b" "' + txt + '"\n' + if (mirc.cutoff){ + txt = new String(txt) + txt.cutoff = true + } + return txt +} diff --git a/js/png.js b/js/png.js new file mode 100644 index 0000000..e9b326e --- /dev/null +++ b/js/png.js @@ -0,0 +1,226 @@ +var PNG = (function(){ + +var crc32 = function(u8){ + var table = new Uint32Array([ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + ]) + + var crc = 0 ^ (-1) + + for(var i = 0; i < u8.length; i++){ + crc = (crc >>> 8) ^ table[(crc ^ u8[i]) & 0xFF] + } + + //return (crc ^ (-1)) // signed + return (crc ^ (-1)) >>> 0 +} + +var signature = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]) +var te, td + +// decodes chunks in png +// see http://www.w3.org/TR/PNG/#5Chunk-layout +// returns something like +// [{length: Number, +// type: String[4], +// crc: Number, +// data: Uint8Array[] // optional +// }, ...] +var decode = function(buf, err){ + var u8a = new Uint8Array(buf) + var dv = new DataView(buf) + td = td || new TextDecoder('utf-8') + err = err || function(msg){ throw new Error(msg) } + + var out = [] + var pos = 0 + + if (u8a.length < signature.length) return err("not a valid png") + for (var i=0; i u8a.length) return err("unexpected end of file") + chunk.length = dv.getInt32(pos, false) + pos += 4 + + if (pos + 4 > u8a.length) return err("unexpected end of file") + chunk.type = td.decode(new DataView(buf, pos, 4)) + pos += 4 + + if (chunk.length){ + if (pos + chunk.length > u8a.length) return err('unexpected end of file') + chunk.data = new Uint8Array(buf, pos, chunk.length) + pos += chunk.length + } + + if (pos + 4 > u8a.length) return err("unexpected end of file") + //chunk.crc = new Uint8Array(buf, pos, 4) + chunk.crc = dv.getUint32(pos, false) + pos += 4 + + + out.push(chunk) + //done = true + //console.log(pos.length, u8a.length) + if (pos === u8a.length) done = true + } + + + return out +} + +var encode = function(chunks){ + te = te || new TextEncoder('utf-8') + + var size = 8 // inital png signature + for (var i=0, c; c=chunks[i]; i++){ + size += 4 // length + size += 4 // type + size += c.length // data + size += 4 // crc32 + } + + var buf = new ArrayBuffer(size) + var u8 = new Uint8Array(buf) + var dv = new DataView(buf) + var pos = 0 + + u8.set(signature, 0) + pos += 8 + + for (var i=0, c; c=chunks[i]; i++){ + dv.setInt32(pos, c.length, false) // length + pos += 4 + var chunk_type_u8 = te.encode(c.type) // type + u8.set(chunk_type_u8, pos) + pos += 4 + if (c.length){ + u8.set(c.data, pos) // data + pos += c.length + } + //u8.set(c.crc, pos) // crc32 + dv.setUint32(pos, c.crc, false) // crc32 + pos += 4 + } + + return u8 +} + + +var make_itxt_chunk = function(keyword, txt){ + te = te || new TextEncoder('utf-8') + var keyword_u8 = te.encode(keyword) + var txt_u8 = te.encode(txt) + var header_u8 = new Uint8Array(keyword_u8.length + 5) + header_u8.set(keyword_u8, 0) + // header has keyword, then a null byte and some additional fields + // see http://www.w3.org/TR/PNG/#11iTXt + var chunk = {type: 'iTXt'} + chunk.length = header_u8.length + txt_u8.length + var u8 = new Uint8Array(4 + chunk.length) + // put type and data on the same u8 array so we can calculate crc + u8.set(te.encode(chunk.type), 0) + u8.set(header_u8, 4) + u8.set(txt_u8, header_u8.length + 4) + chunk.crc = crc32(u8) + chunk.data = new Uint8Array(u8.buffer, 4) + return chunk +} + +var read_cstring = function(u8, pos){ + var str = "" + while (pos < u8.length){ + if (u8[pos] === 0) return str + str += String.fromCharCode(u8[pos]) + pos++ + } + return str +} + +var decode_itxt_chunk = function(chunk){ + td = td || new TextDecoder('utf-8') + var data = {} + var pos = 0 + data.keyword = read_cstring(chunk.data, 0) + pos += data.keyword.length + 1 + data.compression = chunk.data[pos] + pos += 1 + data.compression_method = chunk.data[pos] + pos += 1 + data.language = read_cstring(chunk.data, pos) + pos += data.language.length + 1 + data.translated_keyword = read_cstring(chunk.data, pos) + pos += data.translated_keyword.length + 1 + var data_u8 = chunk.data.subarray(pos) + data.data = td.decode(data_u8) + return data +} + +var canvas_to_blob_with_colorcode = function(canvas, cc){ + var u8 = dataUriToUint8Array(canvas.toDataURL()) + var chunks = decode(u8.buffer) + var itxt_chunk = make_itxt_chunk('colorcode', cc) + // assume we wanna insert the chunk very last, just in front of the end + chunks.splice(chunks.length - 1, 0, itxt_chunk) + var blob = new Blob([encode(chunks)], {type: 'image/png'}) + return blob +} + +var exports = {} +exports.crc32 = crc32 +exports.decode = decode +exports.encode = encode +exports.make_itxt_chunk = make_itxt_chunk +exports.decode_itxt_chunk = decode_itxt_chunk +exports.canvas_to_blob_with_colorcode = canvas_to_blob_with_colorcode +return exports + +})() diff --git a/js/shader.js b/js/shader.js new file mode 100644 index 0000000..f0ed548 --- /dev/null +++ b/js/shader.js @@ -0,0 +1,58 @@ +var shader = (function(){ + var fn_str, fn, lex + var exports = {} + var animating = false + + exports.init = function(){ + lex = new Lex (0, 0) + exports.build(demo_shader.innerHTML) + } + exports.build = function (fn_str){ + try { + new_fn = new Function('lex', 'x', 'y', 'w', 'h', 't', fn_str) + new_fn(lex, 0, 0, 1, 1, 0) + } + catch (e) { + throw 'Shader execution error' + } + exports.fn = fn = new_fn + return fn + } + exports.run = function(canvas){ + var t = +new Date + shader.canvas = shader.canvas || canvas + var w = shader.canvas.w, h = shader.canvas.h + shader.canvas.forEach(function(lex, x, y){ + fn(lex, x, y, w, h, t) + lex.build() + }) + } + exports.toggle = function(state){ + animating = typeof state == "boolean" ? state : ! animating + shader_fps_el.classList.toggle('hidden') + return animating + } + exports.pause = function(){ + animating = false + shader_fps_el.classList.add('hidden') + shader.fps_time = 0 + } + exports.play = function(){ + animating = true + shader_fps_el.classList.remove('hidden') + } + exports.animate = function (t){ + requestAnimationFrame(exports.animate) + if (! animating) { return } + if (shader.fps_time){ + var ms = Date.now() - shader.fps_time + fps = 1000 / ms + shader_fps_el.innerHTML = (fps | 0) + ' fps' + } + shader.fps_time = Date.now() + exports.run(canvas) + } + + return exports + +})() diff --git a/js/tool.js b/js/tool.js new file mode 100644 index 0000000..d0053a6 --- /dev/null +++ b/js/tool.js @@ -0,0 +1,170 @@ +var Tool = Model({ + init: function (el) { + this.el = el + this.lex = new Lex (el) + this.name = el.innerHTML + }, + bind: function(){ + var tool = this + tool.el.addEventListener('mousedown', function(e){ + tool.focus() + }) + tool.el.addEventListener('contextmenu', function(e){ + tool.context(e) + }) + if (tool.memorable) { + // console.log(tool.name, localStorage.getItem("ascii.tools." + tool.name) ) + tool.use( localStorage.getItem("ascii.tools." + tool.name) == "true" ) + } + }, + use: function(){}, + context: function(e){}, + done: function(){}, + focus: function(){ + // focused && focused.blur() + current_tool && current_tool.blur() + current_tool = this + this.el.classList.add('focused') + this.use() + if (this.name != 'shader' && is_desktop) { cursor_input.focus() } + }, + blur: function(){ + current_tool = null + this.el.classList.remove('focused') + this.done() + } +}) + +var FileTool = Tool.extend({ + focus: function(){ + if (current_filetool === this) { + this.blur() + return + } + current_filetool && current_filetool.blur() + current_filetool = this + this.el.classList.add('focused') + this.use() + if (this.name != 'shader' && is_desktop) { cursor_input.focus() } + }, + blur: function(){ + current_filetool = null + this.el.classList.remove('focused') + this.done() + } +}) + +var RadioItem = Tool.extend({ + init: function(group, el){ + this.group = group + this.el = el + }, + focus: function(){ + this.el.classList.add('focused') + }, + blur: function(){ + this.el.classList.remove('focused') + }, + bind: function(){ + var control = this + this.el.addEventListener('mousedown', function(){ + control.group.use(control) + }) + } +}) + +var RadioGroup = Tool.extend({ + init: function(el){ + this.el = el + this.controls = {} + var names = el.innerHTML.split(' ') + el.innerHTML = '' + var group = this + names.forEach(function(value){ + var el = document.createElement('span') + el.classList.add('radio','tool') + var control = new RadioItem(group, el) + if (value.substr(0,1) === '*') { + control.value = value = value.substr(1) + group.use(control) + } + control.value = el.innerHTML = value + group.controls[value] = control + group.el.appendChild(el) + }) + }, + use: function(control){ + if (typeof control === 'string') { + control = this.controls[control] + } + this.selected_control && this.selected_control.blur() + this.value = control.value + this.selected_control = control + control.focus() + control.use() + if (this.memorable){ + localStorage.setItem("ascii.tools." + this.name, this.value) + } + }, + bind: function(){ + var tool = this + for (var n in this.controls){ + this.controls[n].bind() + } + if (tool.memorable) { + var value = localStorage.getItem("ascii.tools." + tool.name) + if (value) tool.use(value) + } + } +}) + +var Checkbox = Tool.extend({ + init: function (el){ + this.__init(el) + var name = this.name.replace(/^[x_] /,"") + var state = localStorage.getItem("ascii.tools." + name) == "true" || this.name[0] == "x" + this.name = name + this.update(state) + }, + update: function(state){ + if (state) this.el.innerHTML = "x " + this.name + else this.el.innerHTML = "_ " + this.name + if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) } + } +}) + +var BlurredCheckbox = Checkbox.extend({ + focus: function(){ + this.use() + }, + blur: function(){ + this.el.classList.remove('focused') + this.done() + } +}) + +var BlurredTool = Tool.extend({ + focus: function(){ + this.use() + }, + blur: function(){ + this.el.classList.remove('focused') + this.done() + } +}) + +var HiddenCheckbox = BlurredCheckbox.extend({ + on: "o", + off: ".", + init: function (el){ + this.el = el + this.lex = new Lex (el) + this.name = this.el.id + var state = localStorage.getItem("ascii.tools." + name) == "true" || this.el.innerHTML[0] == this.on + this.update(state) + }, + update: function(state){ + this.el.innerHTML = state ? this.on : this.off + if (this.memorable) { localStorage.setItem("ascii.tools." + this.name, !! state) } + } +}) diff --git a/js/ui/brush.js b/js/ui/brush.js new file mode 100644 index 0000000..668538f --- /dev/null +++ b/js/ui/brush.js @@ -0,0 +1,108 @@ +var brush = (function(){ + + var brush = new Matrix (5, 5, function(x,y){ + var lex = new Lex (x,y) + lex.build() + return lex + }) + + brush.modified = false + + brush.mask = blit.circle + + brush.generate = function(){ + brush.fill(brush) + brush.mask(brush) + } + + brush.bind = function(){ + + var last_point = [0,0] + var dragging = false + var erasing = false + + brush.forEach(function(lex, x, y){ + + if (lex.bound) return + lex.bound = true + + var point = [x,y] + lex.span.addEventListener('contextmenu', function(e){ + e.preventDefault() + }) + lex.span.addEventListener('mousedown', function(e){ + e.preventDefault() + current_canvas = brush + brush.modified = true + dragging = true + erasing = (e.which == "3" || e.ctrlKey) + if (erasing) { + lex.clear() + } + else { + fillColor = brush.bg + lex.assign(brush) + } + brush.focus(x, y) + }) + lex.span.addEventListener('mousemove', function(e){ + e.preventDefault() + if (! dragging) { + return + } + erasing = (e.which == "3" || e.ctrlKey) + if (erasing) { + lex.clear() + } + else { + lex.assign(brush) + } + brush.focus(x, y) + }) + }) + window.addEventListener('mouseup', function(e){ + dragging = erasing = false + }) + } + + brush.resize = function(w, h){ + w = this.w = clamp(w, this.min, this.max) + h = this.h = clamp(h, this.min, this.max) + brush.rebuild() + controls.brush_w.char = "" + w + controls.brush_w.build() + controls.brush_h.char = "" + h + controls.brush_h.build() + } + brush.size_add = function(w, h){ + brush.resize(brush.w + w, brush.h + h) + } + brush.expand = function(i){ + brush.size_add(i, i) + } + brush.contract = function(i){ + brush.size_add(-i, -i) + } + + brush.load = function(lex){ + brush.char = lex.char + brush.fg = lex.fg + brush.bg = lex.bg + brush.opacity = 1 + } + + brush.min = 1 + brush.max = 100 + + brush.char = " " + brush.fg = 0 + brush.bg = 1 + brush.opacity = 1 + + brush.draw_fg = true + brush.draw_bg = true + brush.draw_char = true + + return brush + +})() diff --git a/js/ui/canvas.js b/js/ui/canvas.js new file mode 100644 index 0000000..ca14323 --- /dev/null +++ b/js/ui/canvas.js @@ -0,0 +1,144 @@ +var canvas = current_canvas = (function(){ + + var cols = 100 + var rows = 30 + + var canvas = new Matrix (cols, rows, function(x,y){ + var lex = new Lex (x,y) + lex.build() + return lex + }) + + canvas.bind = function(){ + + canvas.forEach(function(lex, x, y){ + + if (lex.bound) return + lex.bound = true + var point = [x,y] + lex.span.addEventListener('contextmenu', function(e){ + e.preventDefault() + }) + lex.span.addEventListener('mousedown', function(e){ + if (is_mobile) return + e.preventDefault() + dragging = true + current_canvas = canvas + if (e.altKey) { + if (e.shiftKey) { + blit.copy_from(canvas, brush, floor(x-brush.w/2), floor(y-brush.h/2)) + brush.mask(brush) + draw.set_last_point(e, point) + } + else { + brush.load(lex) + brush.generate() + dragging = false + } + return + } + else if (drawing) { + undo.new() + draw.down(e, lex, point) + } + else if (selecting) { + selection.down(e, lex, point) + } + else if (transforming) { + transform.down(e, lex, point) + } + else if (filling) { + undo.new() + draw.fill(brush, x, y) + } + canvas.focus(x, y) + }) + + lex.span.addEventListener("mousemove", function(e){ + mouse.x = x + mouse.y = y + if (is_mobile) return + if (! dragging) return + if (drawing) { + draw.move(e, lex, point) + } + else if (selecting) { + selection.move(e, lex, point) + } + else if (transforming) { + transform.move(e, lex, point) + } + canvas.focus(x, y) + }) + + }) + + if (is_mobile) { + canvas.rapper.addEventListener('touchstart', function(e){ + e.preventDefault() + var x, y, point, lex + x = (e.touches[0].pageX - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetWidth + y = (e.touches[0].pageY - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetHeight + x = ~~clamp(x, 0, canvas.aa[0].length-1) + y = ~~clamp(y, 0, canvas.aa.length-1) + point = [x,y] + lex = canvas.aa[y][x] + dragging = true + if (drawing) { + undo.new() + draw.down(e, lex, point) + } + else if (filling) { + undo.new() + draw.fill(brush, x, y) + } + canvas.focus(x, y) + }) + canvas.rapper.addEventListener("touchmove", function(e){ + e.preventDefault() + var x, y, point, lex + x = (e.touches[0].pageX - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetWidth + y = (e.touches[0].pageY - canvas.rapper.offsetTop) / canvas.aa[0][0].span.offsetHeight + x = ~~clamp(x, 0, canvas.aa[0].length-1) + y = ~~clamp(y, 0, canvas.aa.length-1) + point = [x,y] + lex = canvas.aa[y][x] + if (! dragging) return + shader_el.innerHTML = point.join(",") + if (drawing) { + draw.move(e, lex, point) + } + canvas.focus(x, y) + }) + } + + } + + canvas.min = 1 + canvas.max = 999 + + // canvas.resize(1, 1, true) // wont create undo state + canvas.resize = function(w, h, no_undo){ + var old_w = this.w, old_h = this.h + w = this.w = clamp(w, this.min, this.max) + h = this.h = clamp(h, this.min, this.max) + if (old_w === w && old_h === h) return; + + if (!no_undo){ + undo.new() + undo.save_resize(w, h, old_w, old_h) + } + + canvas.__proto__.resize.call(canvas, w, h) + controls.canvas_w.char = "" + w + controls.canvas_w.build() + controls.canvas_h.char = "" + h + controls.canvas_h.build() + } + canvas.size_add = function(w, h){ + canvas.resize(canvas.w + w, canvas.h + h) + } + + return canvas + +})() diff --git a/js/ui/controls.js b/js/ui/controls.js new file mode 100644 index 0000000..f75ee67 --- /dev/null +++ b/js/ui/controls.js @@ -0,0 +1,374 @@ +var controls = (function(){ + + var controls = {} + + controls.cross = new Tool (cross_el) + controls.cross.use = function(){ + if (brush.mask == blit.cross) { + controls.cross.el.innerHTML = "ssoɹɔ" + brush.mask = blit.inverted_cross + } + else { + controls.cross.el.innerHTML = "cross" + brush.mask = blit.cross + } + brush.generate() + drawing = true + brush.modified = false + } + controls.cross.done = function(){ + controls.cross.el.innerHTML = "cross" + drawing = false + } + + controls.circle = new Tool (circle_el) + controls.circle.use = function(){ + brush.mask = blit.circle + brush.generate() + drawing = true + brush.modified = false + } + controls.circle.done = function(){ + drawing = false + } + + controls.square = new Tool (square_el) + controls.square.use = function(){ + brush.mask = blit.square + brush.generate() + brush.modified = false + drawing = true + } + controls.square.done = function(){ + drawing = false + } + + controls.text = new Tool (text_el) + controls.text.use = function(){ + current_filetool && current_filetool.blur() + } + + controls.select = new Tool (select_el) + controls.select.use = function(){ + selection.show() + } + controls.select.done = function(){ + selection.hide() + } + + controls.rotate = new Tool (rotate_el) + controls.rotate.use = function(){ + transform.set_mode('rotate') + } + controls.rotate.done = function(){ + transform.done() + } + + controls.scale = new Tool (scale_el) + controls.scale.use = function(){ + transform.set_mode('scale') + } + controls.scale.done = function(){ + transform.done() + } + + controls.slice = new Tool (slice_el) + controls.slice.use = function(){ + transform.set_mode('slice') + } + controls.slice.done = function(){ + transform.done() + } + + controls.translate = new Tool (translate_el) + controls.translate.use = function(){ + transform.set_mode('translate') + } + controls.translate.done = function(){ + transform.done() + } + + controls.fill = new Tool (fill_el) + controls.fill.use = function(){ + filling = true + document.body.classList.add("bucket") + } + controls.fill.done = function(){ + filling = false + document.body.classList.remove("bucket") + } + + controls.undo = new BlurredTool (undo_el) + controls.undo.use = function(){ + undo.undo() + } + + controls.redo = new BlurredTool (redo_el) + controls.redo.use = function(){ + undo.redo() + } + + controls.clear = new BlurredTool (clear_el) + controls.clear.use = function(){ + undo.new() + undo.save_rect(0, 0, canvas.w, canvas.h) + canvas.erase() + current_filetool && current_filetool.blur() + } + + controls.webcam = new FileTool (webcam_el) + controls.webcam.load = function(){ + this.loaded = true + webcam_close.addEventListener("click", function(){ controls.webcam.blur() }) + window.addEventListener("message", function(e){ + if (e.origin !== window.location.origin) return + controls.webcam.blur() + controls.circle.focus() + import_textarea.value = e.data + clipboard.import_colorcode() + }) + } + controls.webcam.use = function(){ + if (! this.loaded) { + this.load() + } + webcam_iframe.src = "webcam.html" + webcam_rapper.style.display = "block" + } + controls.webcam.done = function(){ + webcam_iframe.src = "" + webcam_rapper.style.display = "none" + } + + controls.grid = new BlurredCheckbox (grid_el) + controls.grid.memorable = true + controls.grid.use = function(state){ + state = typeof state == "boolean" ? state : ! document.body.classList.contains("grid") + document.body.classList[ state ? "add" : "remove" ]('grid') + letters.grid = palette.grid = canvas.grid = state + canvas.resize_rapper() + palette.resize_rapper() + letters.resize_rapper() + if (! selection.hidden) selection.reposition() + this.update( state ) + } + ClipboardTool = FileTool.extend({ + blur: function(){ + this.__blur() + clipboard.hide() + } + }) + controls.save = new ClipboardTool (save_el) + controls.save.use = function(){ + changed && clipboard.upload_png() + clipboard.show() + clipboard.export_mode() + } + controls.send_to_irc = new ClipboardTool (send_to_irc_el) + controls.send_to_irc.use = function(){ + changed && clipboard.upload_png() + clipboard.show() + clipboard.export_mode() + alert('your ascii art is now on display on the IRC channel inside the panke.gallery!') + } + controls.load = new ClipboardTool (load_el) + controls.load.use = function(){ + // console.log("use") + clipboard.show() + clipboard.import_mode() + } + + controls.save_format = new RadioGroup(format_el) + controls.save_format.name = 'save_format' + controls.save_format.memorable = true + var cs = controls.save_format.controls + cs.mirc.use = cs.irssi.use = cs.ascii.use = function(){ + clipboard.export_data() + } + // + + var ShaderTool = FileTool.extend({ + active: false, + use: function(state){ + this.active = typeof state == "boolean" ? state : ! this.active + if (this.active) { + shader_rapper.style.display = "block" + shader_textarea.focus() + } else { + shader_rapper.style.display = "none" + } + }, + done: function(){ + this.use(false) + } + }) + controls.shader = new ShaderTool (shader_el) + shader_textarea.value = shader_textarea.value || demo_shader.innerHTML + shader_textarea.addEventListener("input", function(){ + var fn = shader.build(shader_textarea.value) + fn && shader.run(canvas) + }) + controls.animate = new BlurredCheckbox (animate_checkbox) + controls.animate.use = function(state){ + var state = shader.toggle() + this.update(state) + // controls.shader.focus() + controls.shader.use(true) + } + + controls.shader_target = new RadioGroup(shader_target_el) + var cs = controls.shader_target.controls + cs.canvas.use = function(){ shader.canvas = canvas } + cs.brush.use = function(){ shader.canvas = brush } + cs.selection.use = function(){ shader.canvas = selection.canvas } + + controls.experimental_palette = new HiddenCheckbox (experimental_palette_toggle) + controls.experimental_palette.memorable = true + controls.experimental_palette.use = function(state){ + var state = palette.experimental(state) + this.update(state) + } + + controls.advanced = new BlurredCheckbox (advanced_checkbox) + controls.advanced.memorable = true + controls.advanced.use = function(state){ + console.log(state) + state = typeof state == "boolean" ? state : ! document.body.classList.contains('panke') + if (state) + document.body.classList.add('panke') + else + document.body.classList.remove('panke') + this.update(state) + } + +/* + controls.nopaint = new HiddenCheckbox (nopaint_toggle) + controls.nopaint.memorable = true + controls.nopaint.on = "N" + controls.nopaint.use = function(state){ + var state = nopaint.toggle(state) + this.update(state) + } +*/ + + // + + controls.fg = new BlurredCheckbox (fg_checkbox) + controls.fg.use = function(state){ + brush.draw_fg = state || ! brush.draw_fg + this.update(brush.draw_fg) + } + + controls.bg = new BlurredCheckbox (bg_checkbox) + controls.bg.use = function(state){ + brush.draw_bg = state || ! brush.draw_bg + this.update(brush.draw_bg) + } + + controls.char = new BlurredCheckbox (char_checkbox) + controls.char.use = function(state){ + brush.draw_char = state || ! brush.draw_char + this.update(brush.draw_char) + } + + // + +// controls.turn = new BlurredCheckbox (turn_checkbox) +// controls.turn.memorable = true +// controls.turn.use = function(state){ +// canvas.rotated = typeof state == "boolean" ? state : ! canvas.rotated +// canvas.resize_rapper() +// this.update(canvas.rotated) +// } + + // controls.pixels = new BlurredCheckbox (pixels_checkbox) + // controls.pixels.memorable = true + // controls.pixels.use = function(state){ + // canvas.pixels = typeof state == "boolean" ? state : ! canvas.pixels + // document.body.classList.toggle("pixels", canvas.pixels) + // this.update(canvas.pixels) + // } + + controls.mirror_x = new BlurredCheckbox (mirror_x_checkbox) + controls.mirror_x.use = function(state){ + window.mirror_x = typeof state == "boolean" ? state : ! window.mirror_x + this.update(window.mirror_x) + } + controls.mirror_y = new BlurredCheckbox (mirror_y_checkbox) + controls.mirror_y.use = function(state){ + window.mirror_y = typeof state == "boolean" ? state : ! window.mirror_y + this.update(window.mirror_y) + } + + // + + controls.vertical = new BlurredCheckbox (vertical_checkbox) + controls.vertical.memorable = true + controls.vertical.use = function(state){ + canvas.vertical = typeof state == "boolean" ? state : ! canvas.vertical + controls.vertical.refresh() + } + controls.vertical.refresh = function(){ + if (canvas.vertical) { + document.body.classList.add("vertical") + } + else { + document.body.classList.remove("vertical") + } + palette.repaint() + letters.repaint() + this.update(canvas.vertical) + } + + // + + controls.brush_w = new Lex (brush_w_el) + controls.brush_h = new Lex (brush_h_el) + controls.canvas_w = new Lex (canvas_w_el) + controls.canvas_h = new Lex (canvas_h_el) + + // bind + + controls.bind = function(){ + + for (var n in controls){ + var control = controls[n] + if (typeof control === 'object' && 'bind' in control){ + control.bind() + } + } + + [ + controls.brush_w, + controls.brush_h, + controls.canvas_w, + controls.canvas_h + ].forEach(function(lex){ + lex.span.addEventListener('mousedown', function(e){ + lex.focus() + if (is_mobile) cursor_input.focus() + }) + }); + + controls.brush_w.key = keys.single_numeral_key(controls.brush_w, function(w){ brush.resize(w, brush.h) }) + controls.brush_w.raw_key = keys.arrow_key(function(w){ brush.size_add(w, 0) }) + + controls.brush_h.key = keys.single_numeral_key(controls.brush_h, function(h){ brush.resize(brush.w, h) }) + controls.brush_h.raw_key = keys.arrow_key(function(h){ brush.size_add(0, h) }) + + controls.canvas_w.key = keys.multi_numeral_key(controls.canvas_w, 3) + controls.canvas_w.onBlur = keys.multi_numeral_blur(controls.canvas_w, function(w){ canvas.resize(w, canvas.h) }) + controls.canvas_w.raw_key = keys.arrow_key(function(w){ canvas.size_add(w, 0) }) + + controls.canvas_h.key = keys.multi_numeral_key(controls.canvas_h, 3) + controls.canvas_h.onBlur = keys.multi_numeral_blur(controls.canvas_h, function(h){ canvas.resize(canvas.w, h) }) + controls.canvas_h.raw_key = keys.arrow_key(function(h){ canvas.size_add(0, h) }) + + add_custom_el.addEventListener("click", function(){ + custom.clone() + }) + + } + + return controls +})() diff --git a/js/ui/custom.js b/js/ui/custom.js new file mode 100644 index 0000000..a758d48 --- /dev/null +++ b/js/ui/custom.js @@ -0,0 +1,24 @@ +var custom = (function(){ + + var exports = {} + + exports.clone = function (){ + var new_brush = brush.clone() + var rapper = document.createElement("div") + rapper.className = "custom" + new_brush.append(rapper) + custom_rapper.appendChild(rapper) + // store in localstorage? + rapper.addEventListener("click", function(){ + // load this brush + exports.load(new_brush) + }) + } + + exports.load = function(new_brush){ + brush.assign( new_brush ) + } + + return exports + +})() diff --git a/js/ui/goodies.js b/js/ui/goodies.js new file mode 100644 index 0000000..67f245c --- /dev/null +++ b/js/ui/goodies.js @@ -0,0 +1,132 @@ +var goodies = (function(){ + var goodies = {} + + goodies.build = () => { + Object.keys(goodies.list).map(() => { + goodies_rapper.appendChild(tool_canvas.el) + }) + } + + goodies.list = {} + + goodies.list.choppy = { + mode: 'brush', + fn: `var char = choice(" abcdef ") + lex.bg = +choice("0124") + lex.fg = +choice("01234") + lex.char = char + lex.opacity = char == " " ? 0 : 1`, + } + + goodies.list.foggy = { + mode: 'brush', + fn: `var char = choice(" abcdef ") + lex.bg = choice([14,15]) + lex.fg = choice("367") + lex.char = char + lex.opacity = char == " " ? 0 : 1`, + } + // goodies.list.name = { + // fn: ``, + // } + // goodies.list.name = { + // fn: ``, + // } + // goodies.list.name = { + // fn: ``, + // } + // goodies.list.name = { + // fn: ``, + // } + +// >> mirror brush (up-down) + +// NOTE: Animate this on the canvas, then draw: + +// if (x > h/2) { +// lex.assign( canvas.aa[h-y][x] ) +// } + + + +// >> rainbow stardust brush + +// Uncheck BG and animate this to brush: + +// lex.fg = hue(t) +// lex.char = choice(" ,'.,.','****** ") + + + +// >> noise brushes, works on a black background: + +// lex.bg = max(5, yellow(randint(t))) +// lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +// >> simple rainbow: + +// if (lex.bg != 1) lex.bg = randint(t) +// lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +// >> self-erasing: + +// if (lex.bg != 1) lex.bg = yellow(randint(t)) +// lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +// >> cycling rainbow brush + +// if (lex.bg != 1) lex.bg = hue( all_color_hue_order.indexOf( color_names[ lex.bg ] ) + 1 ) +// lex.opacity = lex.bg == colors.black ? 0 : 1 + + + +// >> "stars" brush.. set your brush to paint just the character "#" + +// if (lex.char == "#") { +// lex.fg = hue(randint(15)) +// lex.char = random() > 0.1 ? " " : "+@*.,\"+'*-"[randint(10)] +// } + + + +// >> use fg char to mask mask what you're drawing on the bg + +// if (lex.char != "/") { lex.bg = 1 } + + + +// >> sharded glitch brush + +// Example: http://asdf.us/z/kksnvs.png + +// Use on a brush: + +// lex.bg = t/y/x +// lex.opacity = lex.bg % 1 ? 0 : 1 + + + +// >> incremental brush + +// Set your brush to be the ^ character, square, about 10x10 +// Draw "char" only +// Then animate this shader on the canvas: + +// if (lex.char=="^") { +// lex.bg += 1 +// lex.char = " " +// } +// lex.bg += 1 + + + + + + return goodies +}) \ No newline at end of file diff --git a/js/ui/keys.js b/js/ui/keys.js new file mode 100644 index 0000000..3cd6168 --- /dev/null +++ b/js/ui/keys.js @@ -0,0 +1,240 @@ +var keys = (function(){ + + var keys = {} + keys.bind = function(){ + cursor_input.addEventListener('keydown', function(e){ + + // console.log("keycode:", e.keyCode) + if (e.altKey) { + document.body.classList.add("dropper") + } + + switch (e.keyCode) { + case 27: // esc + if (!selection.hidden && current_canvas === canvas){ + selection.hide() + selection.show() + } else if (focused){ + focused.blur() + } + return + } + + if (window.focused && focused.raw_key) { + focused.raw_key(e) + return + } + + switch (e.keyCode) { + case 219: // [ + if (current_tool.name != "text") { + e.preventDefault() + brush.contract(1) + brush.modified = false + check_if_lost_focus() + } + break + case 221: // ] + if (current_tool.name != "text") { + e.preventDefault() + brush.expand(1) + brush.modified = false + } + break + case 8: // backspace + e.preventDefault() + if (current_canvas === canvas) + undo.new() + current_canvas.focus_add(-1, 0) + if (current_canvas === canvas) + undo.save_focused_lex() + focused.char = " " + focused.build() + return + case 13: // return + e.preventDefault() + current_canvas.focusLex(focused.y, focused.x+1) + return + case 38: // up + e.preventDefault() + current_canvas.focus_add(0, -1) + break + case 40: // down + e.preventDefault() + current_canvas.focus_add(0, 1) + break + case 37: // left + e.preventDefault() + current_canvas.focus_add(-1, 0) + break + case 39: // right + e.preventDefault() + current_canvas.focus_add(1, 0) + break + // use typical windows and os x shortcuts + // undo: ctrl-z or cmd-z + // redo: ctrl-y or shift-cmd-z + case 89: // y + if (!e.ctrlKey && !e.metaKey) break; + e.preventDefault(); + undo.redo(); + break + case 90: // z + if (!e.ctrlKey && !e.metaKey) break; + e.preventDefault(); + if (e.shiftKey) + undo.redo(); + else + undo.undo(); + break + // default: + // if (focused) { focused.key(undefined, e.keyCode) } + } + }) + + cursor_input.addEventListener('input', function(e){ + /* + if (! e.metaKey && ! e.ctrlKey && ! e.altKey) { + e.preventDefault() + } + */ + if (current_tool.name == "shader") { + cursor_input.value = "" + return + } + var char = cursor_input.value + cursor_input.value = "" + + // console.log("input:", char) + + if (current_tool.name != "text" && ! brush.modified) { + brush.char = char + if (char == " ") { + brush.bg = brush.fg + } + else if (brush.bg != fillColor) { + brush.fg = brush.bg + brush.bg = fillColor + } + brush.rebuild() + } + + if (focused && char) { + var y = focused.y, x = focused.x + if (current_canvas === canvas){ + undo.new() + undo.save_focused_lex() + } + var moving = focused.key(char, e.keyCode) + if ( ! moving || ! ('y' in focused && 'x' in focused) ) { return } + current_canvas.focus_add(1, 0) + } + }) + + cursor_input.addEventListener("keyup", function(e){ + if (! e.altKey) { + document.body.classList.remove("dropper") + } + }) + } + + keys.int_key = function (f) { + return function (key, keyCode) { + var n = parseInt(key) + ! isNaN(n) && f(n) + } + } + + keys.arrow_key = function (fn) { + return function (e){ + switch (e.keyCode) { + case 38: // up + e.preventDefault() + fn(1) + break + case 40: // down + e.preventDefault() + fn(-1) + break + } + } + } + keys.left_right_key = function (fn) { + return function (e){ + switch (e.keyCode) { + case 39: // right + e.preventDefault() + fn(1) + break + case 38: // up + case 40: // down + e.preventDefault() + fn(0) + break + case 37: // left + e.preventDefault() + fn(-1) + break + } + } + } + + keys.single_numeral_key = function (lex, fn) { + return keys.int_key(function(n, keyCode){ + if (n == 0) n = 10 + lex.blur() + fn(n) + }) + } + keys.multi_numeral_key = function (lex, digits){ + return keys.int_key(function(n, keyCode){ + lex.read() + if (lex.char.length < digits) { + n = parseInt(lex.char) * 10 + n + } + lex.char = ""+n + lex.build() + }) + } + keys.multi_numeral_blur = function (lex, fn){ + return function(){ + var n = parseInt(lex.char) + if (isNaN(n)) return + fn(n) + } + } + + // function cancelZoom() { + // var d = document, + // viewport, + // content, + // maxScale = ',maximum-scale=', + // maxScaleRegex = /,*maximum\-scale\=\d*\.*\d*/; + + // // this should be a focusable DOM Element + // if (!this.addEventListener || !d.querySelector) { + // return; + // } + + // viewport = d.querySelector('meta[name="viewport"]'); + // content = viewport.content; + + // function changeViewport(event) { + // // http://nerd.vasilis.nl/prevent-ios-from-zooming-onfocus/ + // viewport.content = content + (event.type == 'blur' ? (content.match(maxScaleRegex, '') ? '' : maxScale + 10) : maxScale + 1); + // } + + // // We could use DOMFocusIn here, but it's deprecated. + // this.addEventListener('focus', changeViewport, true); + // this.addEventListener('blur', changeViewport, false); + // } + + // cancelZoom.bind(cursor_input)(); + + return keys +})() + +function check_if_lost_focus() { + if (! window.focused || ! window.focused.span) + window.focused = canvas.aa[0][0] +} diff --git a/js/ui/letters.js b/js/ui/letters.js new file mode 100644 index 0000000..d745171 --- /dev/null +++ b/js/ui/letters.js @@ -0,0 +1,89 @@ +var letters = (function(){ + + var last_charset = "" + var charset_index = 0 + var charsets = [ + 'Basic Latin', + 'Latin-1 Supplement', + 'Box Drawing', + 'Block Elements', + ] + + var letters = new Matrix (1, 1, function(x,y){ + var lex = new Lex (x,y) + return lex + }) + + letters.charset = "" + + letters.repaint = function(charset){ + letters.charset = charset = charset || last_charset + last_charset = charset + var chars = unicode.block(charset, 32) + if (chars[0] != " ") chars.unshift(" ") + if (canvas.vertical) { + letters.resize( Math.ceil( chars.length / 16 ), 16 ) + } + else { + letters.resize( 32, Math.ceil( chars.length / 32 ) ) + } + + var i = 0 + + letters.forEach(function(lex,x,y){ + if (canvas.vertical) { x=x^y;y=x^y;x=x^y } + var char = chars[i++] + if (palette.chars.indexOf(brush.char) > 1) { + lex.bg = brush.fg + lex.fg = brush.bg + } + else { + lex.bg = colors.black + lex.fg = brush.fg == fillColor ? colors.black : brush.fg + } + lex.char = char + lex.opacity = 1 + lex.build() + }) + } + + letters.bind = function(){ + letters.forEach(function(lex,x,y){ + if (lex.bound) return + lex.bound = true + + lex.span.addEventListener('mousedown', function(e){ + e.preventDefault() + if (e.shiftKey) { + charset_index = (charset_index+1) % charsets.length + letters.repaint(charsets[charset_index]) + return + } + else if (e.ctrlKey || e.which == 3) { + brush.char = lex.char + brush.bg = brush.fg + brush.fg = fillColor + } + else { + brush.char = lex.char + if (lex.char == " ") { + brush.bg = brush.fg + } + else if (brush.bg != fillColor) { + brush.fg = brush.bg + brush.bg = fillColor + } + } + if (! brush.modified) { + brush.generate() + } + palette.repaint() + }) + lex.span.addEventListener('contextmenu', function(e){ + e.preventDefault() + }) + }) + } + + return letters +})() \ No newline at end of file diff --git a/js/ui/nopaint.js b/js/ui/nopaint.js new file mode 100644 index 0000000..9011953 --- /dev/null +++ b/js/ui/nopaint.js @@ -0,0 +1,896 @@ +var nopaint = (function(){ + + controls.no = new Tool (nopaint_no_el) + controls.no.use = function(state){ + undo.undo() + controls.paint.focus() + } + controls.no.context = function(e){ + e.preventDefault() + nopaint.turbo() + } + + controls.paint = new Tool (nopaint_paint_el) + controls.paint.use = function(state){ + nopaint.paint() + nopaint_pause_el.classList.toggle("hidden", false) + focused = controls.paint.lex + } + controls.paint.context = function(e){ + e.preventDefault() + nopaint.autoplay() + } + + controls.nopaint_pause = new Tool (nopaint_pause_el) + controls.nopaint_pause.use = function(state){ + // nopaint.pause() + nopaint.autoplay(false) + nopaint_pause_el.classList.toggle("hidden", true) + focused = canvas.aa[0][0] + } + + // use own stepwise clock to drive tweens + oktween.raf = function(){} + + var nopaint = {} + nopaint.debug = true + nopaint.delay = nopaint.normal_delay = 100 + nopaint.turbo_delay = 0 + nopaint.tool = null + nopaint.tools = {} + nopaint.keys = [] + nopaint.weights = [] + nopaint.step = 0 + nopaint.time = 0 + nopaint.timeout = false + nopaint.toggle = function(state){ + var state = typeof state == "boolean" ? state : nopaint_rapper.classList.contains("hidden") + nopaint_rapper.classList.toggle("hidden", ! state) + nopaint_pause_el.classList.toggle("hidden", true) + document.body.classList.toggle("nopaint", state) + return state + } + nopaint.no = function(){ + undo.undo() + nopaint.paint() + } + nopaint.raw_key = controls.paint.lex.raw_key = keys.left_right_key(function(n){ + if (! nopaint.timeout) return + if (n < 0) nopaint.no() + else if (n > 0) nopaint.paint() + else nopaint.pause() + }) + nopaint.pause = nopaint.blur = function(){ + clearTimeout(nopaint.timeout) + nopaint.timeout = 0 + nopaint.step = 0 + } + nopaint.paint = function(){ + var state = undo.new() + delete state.focus + nopaint.pause() + nopaint.switch_tool() + nopaint.go() + } + nopaint.go = function(){ + nopaint.timeout = setTimeout(nopaint.go, nopaint.delay) + oktween.update(nopaint.time) + nopaint.tool.paint( nopaint.step ) + nopaint.time += 1 + nopaint.step += 1 + } + nopaint.switch_tool = function(){ + last_tool = nopaint.tool + last_tool && last_tool.finish() + nopaint.tool = nopaint.get_random_tool( last_tool ) + nopaint.tool.start( last_tool ) + nopaint.debug && console.log("> %s", nopaint.tool.type) + } + nopaint.add_tool = function(fn){ + nopaint.tools[fn.type] = fn + } + nopaint.disable_all_tools = function(){ + Object.keys(nopaint.tools).forEach(function(key){ + nopaint.tools[key].disabled = true + }) + } + nopaint.enable_tools = function(keys){ + keys.forEach(function(key){ + if (nopaint.tools[key]) nopaint.tools[key].disabled = false + }) + } + nopaint.get_random_tool = function( last_tool ){ + var n = rand( nopaint.sum ) + for (var i = 0, _len = nopaint.weights.length; i < _len; i++) { + if (n < nopaint.weights[i] && (! last_tool || nopaint.keys[i] !== last_tool.key)) { + return nopaint.tools[ nopaint.keys[i] ] + } + } + return nopaint.tools[ choice(nopaint.keys) ] + } + nopaint.regenerate_weights = function(){ + nopaint.sum = 0 + nopaint.weights = [] + nopaint.keys = Object.keys( nopaint.tools ).sort(function(a,b){ + return nopaint.tools[b].opt.weight-nopaint.tools[a].opt.weight + }).filter(function(key){ + return ! nopaint.tools[key].disabled + }) + nopaint.keys.forEach(function(key){ + nopaint.sum += nopaint.tools[key].opt.weight + nopaint.weights.push( nopaint.sum ) + }) + } + + nopaint.is_turbo = false + nopaint.turbo = function(state){ + nopaint.is_turbo = typeof state == "boolean" ? state : ! nopaint.is_turbo + nopaint.delay = nopaint.is_turbo ? nopaint.turbo_delay : nopaint.normal_delay + if (nopaint.is_turbo) { + nopaint_no_el.classList.add("locked") + } + else { + nopaint_no_el.classList.remove("locked") + } + } + + nopaint.is_autoplay = false + nopaint.autoplay = function(state){ + nopaint.is_autoplay = typeof state == "boolean" ? state : ! nopaint.is_autoplay + if (nopaint.is_autoplay) { + nopaint_paint_el.classList.add("locked") + if (! nopaint.player) { + nopaint.player = new RandomPlayer () + } + if (! nopaint.timeout) nopaint.paint() + nopaint.player.play() + } + else { + nopaint_paint_el.classList.remove("locked") + nopaint.pause() + nopaint.player && nopaint.player.pause() + } + } + + var NopaintPlayer = Model({ + type: "player", + upload_png: false, + upload_interval: 100, + step: 0, + timeout: null, + delay: function(){ + return nopaint.is_turbo ? randrange(150, 300) : randrange(400, 800) + }, + reset: function(){ + this.no_count = 0 + this.paint_count = 0 + }, + pause: function(){ + clearTimeout(this.timeout) + this.timeout = 0 + }, + play: function(){ + clearTimeout(this.timeout) + var delay = this.delay() + this.timeout = setTimeout(this.play.bind(this), delay) + this.check_fitness() + this.step += 1 + }, + check_fitness: function(){ + switch (this.fitness()) { + case "no": + nopaint.no_count += 1 + nopaint.since_last_no = 0 + nopaint.since_last_paint += 1 + nopaint.no() + break + case "paint": + nopaint.paint_count += 1 + nopaint.since_last_no += 1 + nopaint.since_last_paint = 0 + nopaint.paint() + break + case "screenshot": + if (this.save_as_png) break + console.log("uploading...") + setTimeout(clipboard.upload_png, 0) + // fall thru + default: + nopaint.since_last_no += 1 + nopaint.since_last_paint += 1 + break + } + }, + fitness: function(){}, + }) + + var RandomPlayer = NopaintPlayer.extend({ + type: "random_player", + upload_png: false, + fitness: function(){ + var no_prob = random() + var paint_prob = 1 - no_prob + if (paint_prob < 0.3) { + return "paint" + } + else if (no_prob < 0.5) { + return "no" + } + else if ( this.paint_count > 100 && (this.step % 100) == 99 ) { + return "screenshot" + } + } + }) + + var StylePlayer = NopaintPlayer.extend({ + type: "style_player", + upload_png: false, + fitness: function(){ + var no_prob = random() + var paint_prob = 1 - no_prob + var np, pp + var steps = this.since_last_paint + + if (nopaint.tool.is_brush) { + if (nopaint.tool.is_clone) { + if (steps < randrange(3,8)) return + np = 0.3 + pp = 0.4 + } + else if (nopaint.tool.is_erase) { + if (steps < randrange(2,6)) return + np = 0.3 + pp = 0.4 + } + else { + if (steps < randrange(2,4)) return + np = 0.1 + pp = 0.3 + } + } + if (nopaint.tool.is_shader) { + switch (nopaint.tool.name) { + case "rotate": + case "scale": + if (steps < randrange(2,4)) return + np = 0.1 + pp = 0.2 + break + default: + np = 0.2 + pp = 0.2 + } + } + if (nopaint.tool.is_fill) { + np = 0.4 + pp = 0.1 + } + + if (steps > 10) { + np *= 0.7 + pp *= 1.5 + + if (nopaint.is_turbo) { + np *= 1.2 + pp *= 1.2 + } + } + + if (paint_prob < np) { + return "paint" + } + else if (no_prob < np) { + return "no" + } + else if ( this.paint_count > 100 && (this.step % 100) == 99 ) { + return "screenshot" + } + } + }) + + /* Base models for brushes */ + + var NopaintTool = Model({ + type: "none", + init: function(opt){ + this.opt = opt || {} + }, + start: function(){}, + paint: function(t){}, + update: function(t){}, + finish: function(){}, + }) + + var NopaintBrush = NopaintTool.extend({ + type: "brush", + is_brush: true, + init: function(opt){ + this.opt = opt || {} + this.opt.max_radius = this.opt.max_radius || 10 + this.p = {x: randint(canvas.w), y: randint(canvas.h)} + this.fg = 0 + this.bg = 1 + this.char = " " + this.tweens = [] + }, + + start: function(last_brush){ + this.set_brush_mask() + this.toggle_channels() + this.reset( last_brush ) + this.regenerate() + draw.down({}, null, [this.p.x, this.p.y]) + }, + + paint: function(t){ + this.update(t) + draw.move_toroidal({}, null, [this.p.x, this.p.y]) + }, + + finish: function(){ + this.tweens.forEach(function(t){ t.cancel() }) + this.tweens = [] + }, + + reorient: function(last_brush){ + var a = {}, b + + if (last_brush) { + this.p.x = a.x = randint(canvas.w) + this.p.y = a.y = randint(canvas.h) + } + else { + a.x = this.p.x + a.y = this.p.y + } + + b = this.get_next_point() + + var tween = oktween.add({ + obj: this.p, + from: a, + to: b, + duration: b.duration, + easing: b.easing, + update: b.update, + finished: function(){ + this.iterate() + this.regenerate() + }.bind(this) + }) + this.tweens.push(tween) + }, + + get_next_point: function(){ + var radius = randrange(2, this.opt.max_radius) + var b = {} + b.duration = randrange(1, 7) + b.easing = choice(easings) + b.x = this.p.x + randrange(-radius, radius) + b.y = this.p.y + randrange(-radius, radius) + return b + }, + + set_brush_mask: function(){ + var r = Math.random() + if (r < 0.2) { + brush.mask = blit.square + } + else if (r < 0.6) { + brush.mask = blit.circle + } + else if (r < 0.9) { + brush.mask = blit.cross + } + else{ + brush.mask = blit.inverted_cross + } + }, + + toggle_channels: function(){ + if (Math.random() < 0.001) { controls.bg.use(false) } + else if (! brush.draw_bg && Math.random() < 0.25) { controls.bg.use(true) } + + if (Math.random() < 0.1) { controls.fg.use(false) } + else if (! brush.draw_fg && Math.random() < 0.5) { controls.fg.use(true) } + + if (Math.random() < 0.02) { controls.char.use(false) } + else if (! brush.draw_char && Math.random() < 0.2) { controls.char.use(true) } + }, + + iterate: function( last_brush ){ + this.reorient( last_brush ) + }, + + regenerate: function(){ + brush.load( this ) + brush.generate() + } + }) + + var easings = "linear circ_out circ_in circ_in_out quad_in quad_out quad_in_out".split(" ") + + /* Standard brushes */ + + var SolidBrush = NopaintBrush.extend({ + type: "solid", + + recolor: function(){ + this.fg = this.bg = randint(16) + this.char = " " + }, + + resize: function(m,n){ + m = m || 3 + n = n || 0 + var bw = xrandrange(5, 0, m) + n + brush.resize( round(bw * randrange(0.9, 1.8)) || 1, round(bw) || 1 ) + }, + + reset: function( last_brush ){ + this.opt.max_radius = randrange(5,20) + this.resize() + this.reorient( last_brush ) + this.recolor( last_brush ) + this.regenerate() + }, + + iterate: function( last_brush ){ + this.resize() + this.reorient( last_brush ) + }, + }) + + var EraseBrush = SolidBrush.extend({ + type: "erase", + reset: function( last_brush ){ + this.opt.max_radius = randrange(8, 20) + this.reorient( last_brush ) + this.bg = random() < 0.2 ? colors.white : colors.black + this.char = " " + brush.load( this ) + this.resize(3,2) + }, + }) + + var ShadowBrush = NopaintBrush.extend({ + type: "shadow", + pairs: [ + [ colors.yellow, colors.orange ], + [ colors.orange, colors.darkred ], + [ colors.red, colors.darkred ], + [ colors.lime, colors.green ], + [ colors.cyan, colors.teal ], + [ colors.cyan, colors.blue ], + [ colors.blue, colors.darkblue ], + [ colors.magenta, colors.purple ], + [ colors.lightgray, colors.darkgray ], + [ colors.darkgray, colors.black ], + [ colors.white, colors.lightgray ], + [ colors.white, colors.black ], + ], + shapes: [ + [[0],[1]], + [[0,0],[1,1]], + [[1,0,0],[1,1,1]], + [[0,0,1],[1,1,1]], + [[0,0,0],[1,1,1]], + [[0,0,0,0],[1,1,1,1]], + [[1,0,0,0],[null,1,1,1]], + [[0,0,0,1],[1,1,1,null]], + [[0,0],[1,0],[1,1]], + [[0,0],[0,1],[1,1]], + ], + reset: function( last_brush ){ + var pair = choice(this.pairs) + var shape = choice(this.shapes) + this.reorient( last_brush ) + brush.char = " " + brush.resize(shape[0].length, shape.length) + brush.generate() + brush.rebuild() + brush.forEach(function(lex,x,y){ + if (shape[y][x] == null) { + lex.opacity = 0 + } + else { + lex.fg = lex.bg = pair[ shape[y][x] ] + lex.opacity = 1 + } + lex.build() + }) + }, + regenerate: function(){}, + }) + + var RandomBrush = SolidBrush.extend({ + type: "random", + iterate: function( last_brush ){ + this.reorient( last_brush ) + this.recolor( last_brush ) + }, + }) + + var HueBrush = SolidBrush.extend({ + type: "hue", + recolor: function(){ + this.fg = this.bg = rand_hue() + this.char = " " + }, + }) + + var GrayBrush = SolidBrush.extend({ + type: "gray", + recolor: function(){ + this.fg = this.bg = rand_gray() + this.char = " " + }, + }) + + var LetterBrush = SolidBrush.extend({ + type: "letter", + recolor: function(){ + this.fg = rand_hue() + this.bg = rand_hue() + this.char = choice( unicode.block(letters.charset, 32) ) + }, + }) + + var RandomLetterBrush = LetterBrush.extend({ + type: "random-letter", + iterate: function(){ + if (Math.random() < 0.01) { + this.fg += 1 + } + if (Math.random() < 0.05) { + var n = this.fg + this.fg = this.bg + this.bg = n + } + if (Math.random() < 0.7) { + this.char = choice( unicode.block(letters.charset, 32) ) + } + this.regenerate() + this.__iterate() + }, + update: function(){ + if (Math.random() < 0.3) { + this.char = choice( unicode.block(letters.charset, 32) ) + } + this.regenerate() + }, + }) + + var CloneBrush = SolidBrush.extend({ + type: "clone", + + is_clone: true, + + reset: function( last_brush ){ + this.opt.max_radius = randrange(5, 20) + this.reorient( last_brush ) + this.resize(4,2) + this.clone_random_region() + }, + + clone_random_region: function(x, y){ + var x = randrange(0, canvas.w - brush.w) + var y = randrange(0, canvas.h - brush.h) + this.clone_region(x, y) + }, + + clone_region: function(x, y){ + blit.copy_toroidal_from(canvas, brush, round(x-brush.w/2), round(y-brush.h/2)) + brush.mask(brush) + }, + + iterate: function( last_brush ){ + this.reorient( last_brush ) + }, + + regenerate: function(){}, + }) + + var SmearBrush = CloneBrush.extend({ + type: "smear", + + update: function(){ + var r = random() + var jitter_x = randnullsign() * xrand(2, 2) + var jitter_y = randnullsign() * xrand(2, 2) + this.clone_region( this.p.x + jitter_x, this.p.y + jitter_y ) + }, + + iterate: function( last_brush ){ + this.resize(4, 2) + this.update() + this.reorient( last_brush ) + } + }) + + var StarsTool = NopaintBrush.extend({ + type: "stars", + chars: "....,,'''*", + + start: function(last_brush){ + this.reorient( last_brush ) + }, + + paint: function(t){ + if (Math.random() < 0.5) { + var lex = canvas.get(this.p.x, this.p.y) + undo.save_lex(lex.x, lex.y, lex) + lex.fg = rand_hue() + // lex.bg = colors.black + lex.char = choice(this.chars) + lex.build() + } + }, + }) + + /* Fill tool */ + + var FillTool = NopaintTool.extend({ + type: "fill", + rate: 25, + is_fill: true, + start: function(){ + this.fill() + }, + paint: function(t){ + if ((t % this.rate) == this.rate-1) { + this.fill() + } + }, + recolor: function(){ + this.fg = this.bg = randint(16) + this.char = " " + this.opacity = 1 + }, + fill: function(){ + var x = randint(canvas.w) + var y = randint(canvas.h) + this.recolor() + draw.fill(this, x, y) + } + }) + + var FillLetterTool = FillTool.extend({ + type: "fill-letter", + rate: 25, + recolor: function(){ + this.fg = randint(16) + this.bg = randint(16) + this.char = choice( unicode.block(letters.charset, 32) ) + this.opacity = 1 + }, + }) + + /* Shader Tools */ + + var ShaderTool = NopaintTool.extend({ + type: "shader", + speed: 3, + is_shader: true, + is_recursive: false, + start: function(){ + undo.save_rect(0, 0, canvas.w, canvas.h) + this.canvas = canvas.clone() + }, + paint: function(t){ + if ((t % this.speed) == 0) { + var w = canvas.w + var h = canvas.h + var lex + if (this.is_recursive) { + this.canvas.assign(canvas) + } + this.before_shade() + for (var x = 0; x < w; x++) { + for (var y = 0; y < h; y++) { + lex = canvas.get(x, y) + if (! this.shade( this.canvas, canvas, lex, x, y, w, h )) { + lex.build() + } + } + } + } + }, + before_shade: function(){}, + shade: function(src, dest, lex, x, y, w, h){}, + finish: function(){ + this.canvas.demolish() + } + }) + + var ColorizeTool = ShaderTool.extend({ + type: "colorize", + fns: [mirc_color_reverse,hue,inv_hue,gray,fire,red,yellow,green,blue,purple,dark_gray], + speed: 5, + start: function(){ + this.__start() + this.i = randint(this.fns.length) + }, + before_shade: function(){ + this.i = (this.i + 1) % this.fns.length + this.fn = this.fns[this.i] + }, + shade: function(src, dest, lex, x, y, w, h){ + lex.bg = this.fn( lex.bg ) + return false + }, + }) + + var TranslateTool = ShaderTool.extend({ + type: "translate", + dx: 0, + dy: 0, + speed: 3, + start: function(){ + this.__start() + this.dx = randint(3)-1 + this.dy = randint(3)-1 + this.x = this.y = 0 + if (! this.dx && ! this.dy) { + this.dx = 1 + this.dy = 0 + } + }, + before_shade: function(){ + this.x += this.dx + this.y += this.dy + }, + shade: function(src, dest, lex, x, y, w, h){ + var copy = src.get(x+this.x, y+this.y) + lex.assign(copy) + return true + }, + }) + + var SliceTool = ShaderTool.extend({ + type: "slice", + dx: 0, + dy: 0, + speed: 1, + is_recursive: true, + start: function(){ + this.__start() + this.is_y = Math.random() > 0.3 + this.limit = this.is_y ? canvas.h : canvas.w + this.position = randint(this.limit) + this.direction = 1 + }, + before_shade: function(){ + if (Math.random() < 0.6) { + this.position = mod(this.position + 1, this.limit) + } + if (Math.random() > 0.8) { + this.direction = randsign() + } + }, + shade: function(src, dest, lex, x, y, w, h){ + if (this.is_y) { + if (y >= this.position) { + var copy = src.get(x + this.direction, y) + lex.assign(copy) + } + } + else if (x >= this.position) { + var copy = src.get(x, y + this.direction) + lex.assign(copy) + } + return true + }, + }) + + var ScaleTool = ShaderTool.extend({ + type: "scale", + scale: 1, + dscale: 0, + speed: 3, + start: function(){ + this.__start() + var sign = randsign() + this.x_scale = 1 + this.y_scale = 1 + this.dx_scale = randsign() * randrange(0.0005, 0.01) + var r = Math.random() + if (r < 0.333) { + this.dy_scale = this.dx_scale * randrange(0.85, 1.25) + } + else if (r < 0.666) { + this.dy_scale = this.dx_scale + } + else { + this.dy_scale = randsign() * randrange(0.0005, 0.01) + } + }, + before_shade: function(){ + this.x_scale += this.dx_scale + this.y_scale += this.dy_scale + }, + shade: function(src, dest, lex, x, y, w, h){ + x = (x/w) * 2 - 1 + y = (y/h) * 2 - 1 + x *= this.x_scale + y *= this.y_scale + x = (x + 1) / 2 * w + y = (y + 1) / 2 * h + var copy = src.get(x, y) + lex.assign(copy) + return true + }, + }) + + var RotateTool = ShaderTool.extend({ + type: "rotate", + theta: 0, + d_theta: 0, + + start: function(){ + this.__start() + var sign = randsign() + this.theta = 0 + this.d_theta = randsign() * randrange(0.001, 0.05) + }, + before_shade: function(){ + this.theta += this.d_theta + }, + shade: function(src, dest, lex, x, y, w, h){ + x = (x/w) * 2 - 1 + y = (y/h) * 2 - 1 + var ca = cos(this.theta) + var sa = sin(this.theta) + var a = x * ca - y * sa + var b = x * sa + y * ca + x = (a + 1) / 2 * w + y = (b + 1) / 2 * h + var copy = src.get(x, y) + lex.assign(copy) + return true + }, + }) + + var CycleTool = ShaderTool.extend({ + type: "cycle", + n: 0, + speed: 5, + is_recursive: true, + start: function(){ + this.__start() + this.n = randsign() + if (random() < 0.2) this.n *= randint(15) + }, + shade: function(src, dest, lex, x, y){ + lex.bg += this.n + return false + }, + }) + + nopaint.add_tool( new SolidBrush({ weight: 5 }) ) + nopaint.add_tool( new ShadowBrush({ weight: 10 }) ) + nopaint.add_tool( new EraseBrush({ weight: 5 }) ) + nopaint.add_tool( new RandomBrush({ weight: 4 }) ) + nopaint.add_tool( new HueBrush({ weight: 5 }) ) + nopaint.add_tool( new GrayBrush({ weight: 5 }) ) + nopaint.add_tool( new LetterBrush({ weight: 2 }) ) + nopaint.add_tool( new RandomLetterBrush({ weight: 12 }) ) + nopaint.add_tool( new CloneBrush({ weight: 8 }) ) + nopaint.add_tool( new SmearBrush({ weight: 10 }) ) + nopaint.add_tool( new FillTool({ weight: 3 }) ) + nopaint.add_tool( new FillLetterTool({ weight: 6 }) ) + nopaint.add_tool( new StarsTool({ weight: 2 }) ) + nopaint.add_tool( new TranslateTool({ weight: 4 }) ) + nopaint.add_tool( new CycleTool({ weight: 1 }) ) + nopaint.add_tool( new ScaleTool({ weight: 3 }) ) + nopaint.add_tool( new RotateTool({ weight: 3 }) ) + nopaint.add_tool( new SliceTool({ weight: 4 }) ) + nopaint.add_tool( new ColorizeTool({ weight: 1 }) ) + nopaint.regenerate_weights() + + nopaint.toggle(true) + + nopaint.player = new StylePlayer () + + return nopaint +})() diff --git a/js/ui/palette.js b/js/ui/palette.js new file mode 100644 index 0000000..40da8b6 --- /dev/null +++ b/js/ui/palette.js @@ -0,0 +1,106 @@ +var palette = (function(){ + + var palette = new Matrix (32, 2, function(x,y){ + var lex = new Lex (x,y) + return lex + }) + + var palette_index = localStorage.getItem("ascii.palette") || 1 + var palette_list = [all_hue, all_inv_hue, mirc_color, mirc_color_reverse] + var palette_fn = palette_list[palette_index] + palette.chars = " " + dither.a + dither.b + dither.c + + palette.repaint = function(){ + var xw = use_experimental_palette ? 5 : 2 + if (canvas.vertical) { + palette.resize( xw, 16 ) + } + else { + palette.resize( 32, xw ) + } + + palette.forEach(function(lex,x,y){ + if (canvas.vertical) { x=x^y;y=x^y;x=x^y;x*=2 } + if (y < 2) { + lex.bg = palette_fn(x>>1) + lex.fg = palette_fn(x>>1) + } + else { + lex.bg = fillColor + lex.fg = palette_fn(x>>1) + } + lex.char = palette.chars[y] + lex.opacity = 1 + lex.build() + if (lex.char == "_") lex.char = " " + }) + } + palette.repaint() + var use_experimental_palette = false + palette.experimental = function(state){ + use_experimental_palette = typeof state == "boolean" ? state : ! use_experimental_palette + use_experimental_palette ? palette.resize(32, 5) : palette.resize(32, 2) + palette.repaint() + return use_experimental_palette + } + + palette.bind = function(){ + palette.forEach(function(lex, x, y){ + if (lex.bound) return + lex.bound = true + + lex.span.addEventListener('mousedown', function(e){ + e.preventDefault() + if (e.shiftKey) { + palette_index = (palette_index+1) % palette_list.length + localStorage.setItem("ascii.palette", palette_index) + palette_fn = palette_list[palette_index] + palette.repaint() + return + } + if (e.ctrlKey || e.which == 3) return + if (brush.char == " " && lex.char == " ") { + brush.fg = lex.fg + brush.bg = lex.bg + brush.char = lex.char + } + else if (lex.char != " ") { + brush.fg = lex.bg + brush.bg = lex.fg + brush.char = lex.char + } + else { + brush.fg = lex.bg + brush.bg = fillColor +// brush.char = lex.char + } + brush.opacity = lex.opacity + if (! brush.modified) { + brush.generate() + } + if (filling || e.ctrlKey) { + fillColor = lex.bg + } + letters.repaint() + }) + + lex.span.addEventListener('contextmenu', function(e){ + e.preventDefault() + fillColor = y ? lex.fg : lex.bg + palette.repaint() + brush.fg = lex.fg + brush.char = lex.char + brush.opacity = lex.opacity + brush.generate() + brush_rapper.style.borderColor = css_reverse_lookup[fillColor] + return + }) + + }) + } + + brush_rapper.style.borderColor = css_reverse_lookup[fillColor] + + return palette + +})() diff --git a/js/ui/selection.js b/js/ui/selection.js new file mode 100644 index 0000000..a2720cb --- /dev/null +++ b/js/ui/selection.js @@ -0,0 +1,159 @@ +var selection = (function(){ + + var creating = false, moving = false, copying = false + + var selection_canvas = new Matrix (1, 1, function(x,y){ + var lex = new Lex (x,y) + lex.build() + return lex + }) + + var selector_el = document.createElement("div") + selector_el.className = "selector_el" + selection_canvas.append(selector_el) + document.body.appendChild(selector_el) + + // in selection mode.. + // - we start by clicking the canvas. this positions the selection, and copies + // the character + // - then we drag down and to the right. this resizes the selection and pushes new + // rows and columns. each of these copies the character underneath. + // - on mouseup, the selection is locked. then.. + // - drag the selection to move it -- this "cuts" it and leaves a blank space on the canvas. + // - shift-drag the selection to copy it + + var a = [0, 0] + var b = [0, 0] + var c = [0, 0] + var d = [0, 0] + + function reset () { + a[0] = a[1] = b[0] = b[1] = 0 + } + function left (a,b) { return min(a[0],b[0]) } + function top (a,b) { return min(a[1],b[1]) } + function right (a,b) { return max(a[0],b[0]) } + function bottom (a,b) { return max(a[1],b[1]) } + function width (a,b) { return abs(a[0]-b[0])+1 } + function height (a,b) { return abs(a[1]-b[1])+1 } + function mag_x (a,b) { return a[0]-b[0] } + function mag_y (a,b) { return a[1]-b[1] } + function orient (a,b) { + var l = left(a,b), m = top(a,b), n = right(a,b), o = bottom(a,b) + a[0] = l ; a[1] = m ; b[0] = n ; b[1] = o + } + + function contains (a,b,point) { + var contains_x = a[0] <= point[0] && point[0] <= b[0] + var contains_y = a[1] <= point[1] && point[1] <= b[1] + return (contains_x && contains_y) + } + function reposition (aa, bb) { + aa = aa || a + bb = bb || b + var cell = canvas.aa[top(aa, bb)][left(aa, bb)].span + var cell_left = cell.offsetLeft + var cell_top = cell.offsetTop + var cell_width = cell.offsetWidth + var cell_height = cell.offsetHeight + + var w = width(aa, bb) + var h = height(aa, bb) + + selector_el.style.top = (cell_top-1) + "px" + selector_el.style.left = (cell_left-1) + "px" + selector_el.style.width = (cell_width*w+1) + "px" + selector_el.style.height = (cell_height*h+1) + "px" + } + function down (e, lex, point){ + if ( ! contains(a,b,point) ) { + copying = false + moving = false + creating = true + a[0] = point[0] + a[1] = point[1] + b[0] = point[0] + b[1] = point[1] + reposition(a,b) + selection.hidden = false + selector_el.classList.add("creating") + } else { + copying = false + moving = true + creating = false + c[0] = point[0] + c[1] = point[1] + d[0] = point[0] + d[1] = point[1] + } + show() + selector_el.classList.remove("dragging") + } + function move (e, lex, point){ + if (creating) { + b[0] = point[0] + b[1] = point[1] + reposition(a,b) + } + else if (moving) { + d[0] = point[0] + d[1] = point[1] + var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] ) + var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] ) + reposition( [ a[0] + dx, a[1] + dy ], [ b[0] + dx, b[1] + dy ]) + } + else if (copying) { + } + } + function up (e) { + if (creating) { + orient(a,b) + selection_canvas.resize(width(a,b), height(a,b)) + reposition(a,b) + blit.copy_from( canvas, selection_canvas, a[0], a[1] ) + selection_canvas.build() + selector_el.classList.remove("creating") + } + if (moving) { + var dx = - clamp( mag_x(c,d), b[0] - canvas.w + 1, a[0] ) + var dy = - clamp( mag_y(c,d), b[1] - canvas.h + 1, a[1] ) + a[0] += dx + a[1] += dy + b[0] += dx + b[1] += dy + undo.new() + undo.save_rect(a[0], a[1], b[0] - a[0] + 1, b[1] - a[1] + 1) + blit.copy_to( canvas, selection_canvas, a[0], a[1] ) + } + if (copying) { + } + creating = moving = copying = false + selector_el.classList.remove("dragging") + } + + function show () { + selecting = true + } + function hide () { + reset() + selector_el.style.top = "-9999px" + selector_el.style.left = "-9999px" + selector_el.style.width = "0px" + selector_el.style.height = "0px" + creating = moving = copying = false + selection.hidden = true + selecting = false + } + + var selection = {} + selection.reposition = reposition + selection.down = down + selection.move = move + selection.up = up + selection.canvas = selection_canvas + selection.show = show + selection.hide = hide + selection.hidden = true + return selection + +})() diff --git a/js/ui/transform.js b/js/ui/transform.js new file mode 100644 index 0000000..d6253bc --- /dev/null +++ b/js/ui/transform.js @@ -0,0 +1,176 @@ +var transform = (function(){ + + var p = [0,0], q = [0,0] + var mode + var copy + + function down (e, lex, point){ + p[0] = point[0] + p[1] = point[1] + q[0] = e.pageX + q[1] = e.pageY + undo.new() + undo.save_rect(0, 0, canvas.w, canvas.h) + copy = canvas.clone() + mode.init(e) + } + function move (e, lex, point){ + var pdx = point[0] - p[0] + var pdy = point[1] - p[1] + var dx = e.pageX - q[0] + var dy = e.pageY - q[1] + var w = canvas.w + var h = canvas.h + mode.before(dx, dy, pdx, pdy, point) + for (var x = 0; x < w; x++) { + for (var y = 0; y < h; y++) { + lex = canvas.get(x, y) + if (! mode.shade( copy, canvas, lex, x, y, w, h )) { + lex.build() + } + } + } + } + function up (e){ + } + + var modes = { + + rotate: { + init: function(e){ + mode.theta = 0 + }, + before: function(dx, dy){ + var radius = dist(0, 0, dx, dy) + if (radius < 10) return + mode.theta = angle(0, 0, dx, -dy) + }, + shade: function(src, dest, lex, x, y, w, h){ + x = (x/w) * 2 - 1 + y = (y/h) * 2 - 1 + var ca = cos(mode.theta) + var sa = sin(mode.theta) + var a = x * ca - y * sa + var b = x * sa + y * ca + x = (a + 1) / 2 * w + y = (b + 1) / 2 * h + var copy = src.get(x, y) + lex.assign(copy) + return true + }, + }, + + scale: { + init: function(e){ + mode.independent = e.shiftKey || e.altKey || e.metaKey + mode.x_scale = mode.y_scale = 0 + }, + before: function(dx, dy, pdx, pdy){ + if (mode.independent) { + mode.x_scale = Math.pow(2, -pdx / (canvas.w / 8)) + mode.y_scale = Math.pow(2, -pdy / (canvas.h / 8)) + } + else { + mode.x_scale = mode.y_scale = Math.pow(2, -pdx / (canvas.w / 8)) + } + }, + shade: function(src, dest, lex, x, y, w, h){ + x = ((x-p[0])/w) * 2 - 1 + y = ((y-p[1])/h) * 2 - 1 + x *= mode.x_scale + y *= mode.y_scale + x = (x + 1) / 2 * w + y = (y + 1) / 2 * h + var copy = src.get(x+p[0], y+p[1]) + lex.assign(copy) + return true + }, + }, + + translate: { + init: function(e){ + mode.dx = mode.dy = 0 + }, + before: function(dx, dy, pdx, pdy){ + mode.dx = -pdx + mode.dy = -pdy + }, + shade: function(src, dest, lex, x, y, w, h){ + var copy = src.get(x+mode.dx, y+mode.dy) + lex.assign(copy) + return true + }, + }, + + slice: { + init: function(e){ + mode.is_y = ! (e.altKey || e.metaKey) + mode.reverse = !! (e.shiftKey) + mode.position = 0 + mode.direction = 0 + mode.last_dd = -1 + }, + before: function(dx, dy, pdx, pdy, point){ + var new_position = mode.is_y ? point[1] : point[0] + var dd = mode.is_y ? pdx : pdy + + if (mode.position !== new_position) { + mode.position = new_position + mode.direction = 0 + } + if (mode.last_dd !== -1) { + mode.direction = mode.last_dd - dd + } + console.log(mode.position) + mode.last_dd = dd + copy.assign(canvas) + }, + shade: function(src, dest, lex, x, y, w, h){ + if (mode.is_y) { + if (y >= mode.position || (mode.reverse && mode.position >= y)) { + var copy = src.get(x + mode.direction, y) + lex.assign(copy) + } + } + else if (x >= mode.position || (mode.reverse && mode.position >= x)) { + var copy = src.get(x, y + mode.direction) + lex.assign(copy) + } + return true + }, + }, + +/* + mode: { + init: function(e){ + }, + before: function(dx, dy, pdx, pdy){ + }, + shade: function(src, dest, lex, x, y, w, h){ + }, + }, +*/ + } + + function set_mode(m){ + if (m in modes) { + mode = modes[m] + transforming = true + } + } + + function done(){ + transforming = false + copy && copy.demolish() + } + + return { + down: down, + move: move, + up: up, + set_mode: set_mode, + modes: modes, + done: done, + } + +})() \ No newline at end of file diff --git a/js/undo.js b/js/undo.js new file mode 100644 index 0000000..35a27d9 --- /dev/null +++ b/js/undo.js @@ -0,0 +1,227 @@ +var undo = (function(){ + +var max_states = 200; + +// undotimetotal = 0; + +var stack = {undo: [], redo: []}; +var current_undo = null; +var dom = {undo: undo_el, redo: redo_el}; +dom.undo.is_visible = dom.redo.is_visible = false + +var LexState = function(lex){ + this.fg = lex.fg; + this.bg = lex.bg; + this.char = lex.char; + this.opacity = lex.opacity; +}; + +var update_dom_visibility = function(type){ + var el = dom[type] + if (el.is_visible){ + if (stack[type].length === 0) { + el.classList.add('hidden') + el.is_visible = false + } + } else if (stack[type].length > 0){ + el.classList.remove('hidden') + el.is_visible = true + } +} +var update_dom = function(){ + update_dom_visibility('undo') + update_dom_visibility('redo') +} + +// state is an undo or redo state that might contain these props +// { lexs: {'0,0': LexState, ...}, // for sparse lex changes (eg brush, fill) +// focus: {x:, y: }, +// size: {w:, h: }, +// rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...] +// } +var new_state = function(){ + var state = {lexs:{}}; + save_focus(canvas.focus_x, canvas.focus_y, state) + return state +} +var new_redo = function(){ + return new_state() +} +var new_undo = function(){ + current_undo = new_state() + stack.redo = [] + stack.undo.push(current_undo) + if (stack.undo.length > max_states) stack.undo.shift(); + update_dom() + return current_undo +} + +var save_focus = function(x, y, state){ + state = state || current_undo + state.focus = {x:x, y:y} +} +var save_size = function(w, h, state){ + state = state || current_undo + state.size = {w:w, h:h}; +} +// the reason for stringifying the x y coords is so that each +// coordinate is saved only once in an undo state. +// otherwise there would be problems with, eg, a brush stroke +// that passed over the same grid cell twice. +var save_lex = function(x, y, lex, state){ + // var start = Date.now() + state = state || current_undo + var lexs = state.lexs; + var xy = x + "," + y; + if (xy in lexs) return; + lexs[xy] = new LexState(lex) + // undotimetotal += Date.now() - start +} +var save_focused_lex = function(state){ + state = state || current_undo + var x = canvas.focus_x + var y = canvas.focus_y + save_lex(x, y, canvas.aa[y][x], state) +} +var save_rect = function(xpos, ypos, w, h, state){ + if (w === 0 || h === 0) return; + state = state || current_undo; + state.rects = state.rects || [] + var aa = canvas.aa; + var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []} + var lexs = rect.lexs + var xlen = xpos + w + var ylen = ypos + h + for (var y = ypos; y < ylen; y++){ + var aay = aa[y] + for (var x = xpos; x < xlen; x++){ + lexs.push(new LexState(aay[x])) + } + } + state.rects.push(rect) +} +var save_resize = function(w, h, old_w, old_h, state){ + state = state || current_undo + save_size(old_w, old_h, state) + if (old_w > w){ + // .---XX + // | XX + // |___XX + save_rect(w, 0, old_w - w, old_h, state) + if (old_h > h){ + // .----. + // | | + // XXXX_| + save_rect(0, h, w, old_h - h, state) + } + } else if (old_h > h){ + // .----. + // | | + // XXXXXX + save_rect(0, h, old_w, old_h - h, state) + } +} + +var restore_state = function(state){ + // all redo states will have a cached undo state on them + // an undo state might have a cached redo state + // if it doesn't have one, generate one + var make_redo = ! ('redo' in state || 'undo' in state); + var aa = canvas.aa + var lex, lexs; + + if (make_redo){ + state.redo = new_redo() + + // copy saved rects that intersect with current canvas size + // important to do this before resizing canvas + if ('rects' in state){ + for (var ri=0, rect; rect=state.rects[ri]; ri++){ + if (rect.x >= canvas.w || + rect.y >= canvas.h) continue; + var w = Math.min(rect.w, canvas.w - rect.x) + var h = Math.min(rect.h, canvas.h - rect.y) + save_rect(rect.x, rect.y, w, h, state.redo) + } + } + if ('size' in state){ + save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo) + } + } + + if ('size' in state){ + canvas.resize(state.size.w, state.size.h, true); + } + + if ('rects' in state){ + for (var ri=0, rect; rect=state.rects[ri]; ri++){ + lexs = rect.lexs + for (var li=0; lex=lexs[li]; li++){ + var x = (li % rect.w) + rect.x + var y = ((li / rect.w)|0) + rect.y + aa[y][x].assign(lex) + } + } + } + + lexs = state.lexs + for (var key in lexs){ + var xy = key.split(','); + lex = aa[xy[1]][xy[0]] + if (make_redo) + save_lex(xy[0], xy[1], lex, state.redo) + lex.assign(lexs[key]) + } + + if ('focus' in state){ + canvas.focus_x = state.focus.x + canvas.focus_y = state.focus.y + if (current_canvas === canvas){ + canvas.focus() + } + } +} + +var undo = function(){ + var state = stack.undo.pop(); + if (!state) return; + + restore_state(state) + + // now take the applied undo state and store it on the redo state + // and push the redo state to the redo stack + state.redo.undo = state + stack.redo.push(state.redo) + delete state.redo + + update_dom() +} + +var redo = function(){ + var state = stack.redo.pop(); + if (!state) return; + + restore_state(state) + + state.undo.redo = state + stack.undo.push(state.undo) + delete state.undo + + update_dom() +} + +return { + stack: stack, + new: new_undo, +// new_redo: new_redo, + save_focus: save_focus, + save_size: save_size, + save_lex: save_lex, + save_focused_lex: save_focused_lex, + save_rect: save_rect, + save_resize: save_resize, + undo: undo, + redo: redo +} + +})() diff --git a/js/unicode.js b/js/unicode.js new file mode 100644 index 0000000..2199750 --- /dev/null +++ b/js/unicode.js @@ -0,0 +1,543 @@ +var unicode = (function(){ + var UNICODE_BLOCK_LIST = [ + 0x0020, 0x007F, "Basic Latin", + 0x0080, 0x00FF, "Latin-1 Supplement", + 0x0100, 0x017F, "Latin Extended-A", + 0x0180, 0x024F, "Latin Extended-B", + 0x0250, 0x02AF, "IPA Extensions", + 0x02B0, 0x02FF, "Spacing Modifier Letters", + 0x0300, 0x036F, "Combining Diacritical Marks", + 0x0370, 0x03FF, "Greek and Coptic", + 0x0400, 0x04FF, "Cyrillic", + 0x0500, 0x052F, "Cyrillic Supplement", + 0x0530, 0x058F, "Armenian", + 0x0590, 0x05FF, "Hebrew", + 0x0600, 0x06FF, "Arabic", + 0x0700, 0x074F, "Syriac", + 0x0750, 0x077F, "Arabic Supplement", + 0x0780, 0x07BF, "Thaana", + 0x07C0, 0x07FF, "NKo", + 0x0800, 0x083F, "Samaritan", + 0x0840, 0x085F, "Mandaic", + 0x08A0, 0x08FF, "Arabic Extended-A", + 0x0900, 0x097F, "Devanagari", + 0x0980, 0x09FF, "Bengali", + 0x0A00, 0x0A7F, "Gurmukhi", + 0x0A80, 0x0AFF, "Gujarati", + 0x0B00, 0x0B7F, "Oriya", + 0x0B80, 0x0BFF, "Tamil", + 0x0C00, 0x0C7F, "Telugu", + 0x0C80, 0x0CFF, "Kannada", + 0x0D00, 0x0D7F, "Malayalam", + 0x0D80, 0x0DFF, "Sinhala", + 0x0E00, 0x0E7F, "Thai", + 0x0E80, 0x0EFF, "Lao", + 0x0F00, 0x0FFF, "Tibetan", + 0x1000, 0x109F, "Myanmar", + 0x10A0, 0x10FF, "Georgian", + 0x1100, 0x11FF, "Hangul Jamo", + 0x1200, 0x137F, "Ethiopic", + 0x1380, 0x139F, "Ethiopic Supplement", + 0x13A0, 0x13FF, "Cherokee", + 0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics", + 0x1680, 0x169F, "Ogham", + 0x16A0, 0x16FF, "Runic", + 0x1700, 0x171F, "Tagalog", + 0x1720, 0x173F, "Hanunoo", + 0x1740, 0x175F, "Buhid", + 0x1760, 0x177F, "Tagbanwa", + 0x1780, 0x17FF, "Khmer", + 0x1800, 0x18AF, "Mongolian", + 0x18B0, 0x18FF, "Unified Canadian Aboriginal Syllabics Extended", + 0x1900, 0x194F, "Limbu", + 0x1950, 0x197F, "Tai Le", + 0x1980, 0x19DF, "New Tai Lue", + 0x19E0, 0x19FF, "Khmer Symbols", + 0x1A00, 0x1A1F, "Buginese", + 0x1A20, 0x1AAF, "Tai Tham", + 0x1AB0, 0x1AFF, "Combining Diacritical Marks Extended", + 0x1B00, 0x1B7F, "Balinese", + 0x1B80, 0x1BBF, "Sundanese", + 0x1BC0, 0x1BFF, "Batak", + 0x1C00, 0x1C4F, "Lepcha", + 0x1C50, 0x1C7F, "Ol Chiki", + 0x1CC0, 0x1CCF, "Sundanese Supplement", + 0x1CD0, 0x1CFF, "Vedic Extensions", + 0x1D00, 0x1D7F, "Phonetic Extensions", + 0x1D80, 0x1DBF, "Phonetic Extensions Supplement", + 0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement", + 0x1E00, 0x1EFF, "Latin Extended Additional", + 0x1F00, 0x1FFF, "Greek Extended", + 0x2000, 0x206F, "General Punctuation", + 0x2070, 0x209F, "Superscripts and Subscripts", + 0x20A0, 0x20CF, "Currency Symbols", + 0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols", + 0x2100, 0x214F, "Letterlike Symbols", + 0x2150, 0x218F, "Number Forms", + 0x2190, 0x21FF, "Arrows", + 0x2200, 0x22FF, "Mathematical Operators", + 0x2300, 0x23FF, "Miscellaneous Technical", + 0x2400, 0x243F, "Control Pictures", + 0x2440, 0x245F, "Optical Character Recognition", + 0x2460, 0x24FF, "Enclosed Alphanumerics", + 0x2500, 0x257F, "Box Drawing", + 0x2580, 0x259F, "Block Elements", + 0x25A0, 0x25FF, "Geometric Shapes", + 0x2600, 0x26FF, "Miscellaneous Symbols", + 0x2700, 0x27BF, "Dingbats", + 0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A", + 0x27F0, 0x27FF, "Supplemental Arrows-A", + 0x2800, 0x28FF, "Braille Patterns", + 0x2900, 0x297F, "Supplemental Arrows-B", + 0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B", + 0x2A00, 0x2AFF, "Supplemental Mathematical Operators", + 0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows", + 0x2C00, 0x2C5F, "Glagolitic", + 0x2C60, 0x2C7F, "Latin Extended-C", + 0x2C80, 0x2CFF, "Coptic", + 0x2D00, 0x2D2F, "Georgian Supplement", + 0x2D30, 0x2D7F, "Tifinagh", + 0x2D80, 0x2DDF, "Ethiopic Extended", + 0x2DE0, 0x2DFF, "Cyrillic Extended-A", + 0x2E00, 0x2E7F, "Supplemental Punctuation", + 0x2E80, 0x2EFF, "CJK Radicals Supplement", + 0x2F00, 0x2FDF, "Kangxi Radicals", + 0x2FF0, 0x2FFF, "Ideographic Description Characters", + 0x3000, 0x303F, "CJK Symbols and Punctuation", + 0x3040, 0x309F, "Hiragana", + 0x30A0, 0x30FF, "Katakana", + 0x3100, 0x312F, "Bopomofo", + 0x3130, 0x318F, "Hangul Compatibility Jamo", + 0x3190, 0x319F, "Kanbun", + 0x31A0, 0x31BF, "Bopomofo Extended", + 0x31C0, 0x31EF, "CJK Strokes", + 0x31F0, 0x31FF, "Katakana Phonetic Extensions", + 0x3200, 0x32FF, "Enclosed CJK Letters and Months", + 0x3300, 0x33FF, "CJK Compatibility", + 0x3400, 0x4DBF, "CJK Unified Ideographs Extension A", + 0x4DC0, 0x4DFF, "Yijing Hexagram Symbols", + 0x4E00, 0x9FFF, "CJK Unified Ideographs", + 0xA000, 0xA48F, "Yi Syllables", + 0xA490, 0xA4CF, "Yi Radicals", + 0xA4D0, 0xA4FF, "Lisu", + 0xA500, 0xA63F, "Vai", + 0xA640, 0xA69F, "Cyrillic Extended-B", + 0xA6A0, 0xA6FF, "Bamum", + 0xA700, 0xA71F, "Modifier Tone Letters", + 0xA720, 0xA7FF, "Latin Extended-D", + 0xA800, 0xA82F, "Syloti Nagri", + 0xA830, 0xA83F, "Common Indic Number Forms", + 0xA840, 0xA87F, "Phags-pa", + 0xA880, 0xA8DF, "Saurashtra", + 0xA8E0, 0xA8FF, "Devanagari Extended", + 0xA900, 0xA92F, "Kayah Li", + 0xA930, 0xA95F, "Rejang", + 0xA960, 0xA97F, "Hangul Jamo Extended-A", + 0xA980, 0xA9DF, "Javanese", + 0xA9E0, 0xA9FF, "Myanmar Extended-B", + 0xAA00, 0xAA5F, "Cham", + 0xAA60, 0xAA7F, "Myanmar Extended-A", + 0xAA80, 0xAADF, "Tai Viet", + 0xAAE0, 0xAAFF, "Meetei Mayek Extensions", + 0xAB00, 0xAB2F, "Ethiopic Extended-A", + 0xAB30, 0xAB6F, "Latin Extended-E", + 0xABC0, 0xABFF, "Meetei Mayek", + 0xAC00, 0xD7AF, "Hangul Syllables", + 0xD7B0, 0xD7FF, "Hangul Jamo Extended-B", + 0xD800, 0xDB7F, "High Surrogates", + 0xDB80, 0xDBFF, "High Private Use Surrogates", + 0xDC00, 0xDFFF, "Low Surrogates", + 0xE000, 0xF8FF, "Private Use Area", + 0xF900, 0xFAFF, "CJK Compatibility Ideographs", + 0xFB00, 0xFB4F, "Alphabetic Presentation Forms", + 0xFB50, 0xFDFF, "Arabic Presentation Forms-A", + 0xFE00, 0xFE0F, "Variation Selectors", + 0xFE10, 0xFE1F, "Vertical Forms", + 0xFE20, 0xFE2F, "Combining Half Marks", + 0xFE30, 0xFE4F, "CJK Compatibility Forms", + 0xFE50, 0xFE6F, "Small Form Variants", + 0xFE70, 0xFEFF, "Arabic Presentation Forms-B", + 0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms", + 0xFFF0, 0xFFFF, "Specials", + 0x10000, 0x1007F, "Linear B Syllabary", + 0x10080, 0x100FF, "Linear B Ideograms", + 0x10100, 0x1013F, "Aegean Numbers", + 0x10140, 0x1018F, "Ancient Greek Numbers", + 0x10190, 0x101CF, "Ancient Symbols", + 0x101D0, 0x101FF, "Phaistos Disc", + 0x10280, 0x1029F, "Lycian", + 0x102A0, 0x102DF, "Carian", + 0x102E0, 0x102FF, "Coptic Epact Numbers", + 0x10300, 0x1032F, "Old Italic", + 0x10330, 0x1034F, "Gothic", + 0x10350, 0x1037F, "Old Permic", + 0x10380, 0x1039F, "Ugaritic", + 0x103A0, 0x103DF, "Old Persian", + 0x10400, 0x1044F, "Deseret", + 0x10450, 0x1047F, "Shavian", + 0x10480, 0x104AF, "Osmanya", + 0x10500, 0x1052F, "Elbasan", + 0x10530, 0x1056F, "Caucasian Albanian", + 0x10600, 0x1077F, "Linear A", + 0x10800, 0x1083F, "Cypriot Syllabary", + 0x10840, 0x1085F, "Imperial Aramaic", + 0x10860, 0x1087F, "Palmyrene", + 0x10880, 0x108AF, "Nabataean", + 0x10900, 0x1091F, "Phoenician", + 0x10920, 0x1093F, "Lydian", + 0x10980, 0x1099F, "Meroitic Hieroglyphs", + 0x109A0, 0x109FF, "Meroitic Cursive", + 0x10A00, 0x10A5F, "Kharoshthi", + 0x10A60, 0x10A7F, "Old South Arabian", + 0x10A80, 0x10A9F, "Old North Arabian", + 0x10AC0, 0x10AFF, "Manichaean", + 0x10B00, 0x10B3F, "Avestan", + 0x10B40, 0x10B5F, "Inscriptional Parthian", + 0x10B60, 0x10B7F, "Inscriptional Pahlavi", + 0x10B80, 0x10BAF, "Psalter Pahlavi", + 0x10C00, 0x10C4F, "Old Turkic", + 0x10E60, 0x10E7F, "Rumi Numeral Symbols", + 0x11000, 0x1107F, "Brahmi", + 0x11080, 0x110CF, "Kaithi", + 0x110D0, 0x110FF, "Sora Sompeng", + 0x11100, 0x1114F, "Chakma", + 0x11150, 0x1117F, "Mahajani", + 0x11180, 0x111DF, "Sharada", + 0x111E0, 0x111FF, "Sinhala Archaic Numbers", + 0x11200, 0x1124F, "Khojki", + 0x112B0, 0x112FF, "Khudawadi", + 0x11300, 0x1137F, "Grantha", + 0x11480, 0x114DF, "Tirhuta", + 0x11580, 0x115FF, "Siddham", + 0x11600, 0x1165F, "Modi", + 0x11680, 0x116CF, "Takri", + 0x118A0, 0x118FF, "Warang Citi", + 0x11AC0, 0x11AFF, "Pau Cin Hau", + 0x12000, 0x123FF, "Cuneiform", + 0x12400, 0x1247F, "Cuneiform Numbers and Punctuation", + 0x13000, 0x1342F, "Egyptian Hieroglyphs", + 0x16800, 0x16A3F, "Bamum Supplement", + 0x16A40, 0x16A6F, "Mro", + 0x16AD0, 0x16AFF, "Bassa Vah", + 0x16B00, 0x16B8F, "Pahawh Hmong", + 0x16F00, 0x16F9F, "Miao", + 0x1B000, 0x1B0FF, "Kana Supplement", + 0x1BC00, 0x1BC9F, "Duployan", + 0x1BCA0, 0x1BCAF, "Shorthand Format Controls", + 0x1D000, 0x1D0FF, "Byzantine Musical Symbols", + 0x1D100, 0x1D1FF, "Musical Symbols", + 0x1D200, 0x1D24F, "Ancient Greek Musical Notation", + 0x1D300, 0x1D35F, "Tai Xuan Jing Symbols", + 0x1D360, 0x1D37F, "Counting Rod Numerals", + 0x1D400, 0x1D7FF, "Mathematical Alphanumeric Symbols", + 0x1E800, 0x1E8DF, "Mende Kikakui", + 0x1EE00, 0x1EEFF, "Arabic Mathematical Alphabetic Symbols", + 0x1F000, 0x1F02F, "Mahjong Tiles", + 0x1F030, 0x1F09F, "Domino Tiles", + 0x1F0A0, 0x1F0FF, "Playing Cards", + 0x1F100, 0x1F1FF, "Enclosed Alphanumeric Supplement", + 0x1F200, 0x1F2FF, "Enclosed Ideographic Supplement", + 0x1F300, 0x1F5FF, "Miscellaneous Symbols and Pictographs", + 0x1F600, 0x1F64F, "Emoticons", + 0x1F650, 0x1F67F, "Ornamental Dingbats", + 0x1F680, 0x1F6FF, "Transport and Map Symbols", + 0x1F700, 0x1F77F, "Alchemical Symbols", + 0x1F780, 0x1F7FF, "Geometric Shapes Extended", + 0x1F800, 0x1F8FF, "Supplemental Arrows-C", + 0x20000, 0x2A6DF, "CJK Unified Ideographs Extension B", + 0x2A700, 0x2B73F, "CJK Unified Ideographs Extension C", + 0x2B740, 0x2B81F, "CJK Unified Ideographs Extension D", + 0x2F800, 0x2FA1F, "CJK Compatibility Ideographs Supplement", + 0xE0000, 0xE007F, "Tags", + 0xE0100, 0xE01EF, "Variation Selectors Supplement", + 0xF0000, 0xFFFFF, "Supplementary Private Use Area-A", + 0x100000, 0x10FFFF, "Supplementary Private Use Area-B", + ] + var UNICODE_BLOCK_COUNT = UNICODE_BLOCK_LIST.length / 3 + var UNICODE_LOOKUP = {} + for (var i = 0, len = UNICODE_BLOCK_LIST.length; i < len; i += 3) { + UNICODE_LOOKUP[ UNICODE_BLOCK_LIST[i+2] ] = [ UNICODE_BLOCK_LIST[i], UNICODE_BLOCK_LIST[i+1] ] + } + + function index (j) { + return [ UNICODE_BLOCK_LIST[j*3], UNICODE_BLOCK_LIST[j*3+1], UNICODE_BLOCK_LIST[j*3+2], [] ] + } + function range(m,n){ + if (m > n) return [] + var a = new Array (n-m) + for (var i = 0, j = m; j <= n; i++, j++) { + a[i] = j + } + return a + } + function paginate (a, n){ + var aa = [], ai, i = 0 + while (i < 100) { + ai = a.slice(i * n, (i+1) * n) + if (! ai.length) break + aa.push(ai) + i++ + } + return aa + } + function block (name, n){ + var b = UNICODE_LOOKUP[name] + if (! b) return "" + return range.apply(null, b).map(function(n){ return String.fromCharCode(n) }) + } + function entities (a) { + return a.map(function(k){ return "&#" + k.join(";&#") + ";" }).join("
") + } + function findGroups (chars){ + var groups = [], row, list + for (var i = 0, j = -1, next = -1, len = chars.length; i < len; i++) { + if (chars[i] < next) { + list.push(chars[i]) + continue + } + do { + j += 1 + next = UNICODE_BLOCK_LIST[(j+1)*3] + } while (chars[i] > next) + row = index(j) + list = row[3] + groups.push( row ) + } + return groups + } + + // encodes unicode characters as escaped utf16 - \xFFFF + // encodes ONLY non-ascii characters + function escapeToUtf16 (txt) { + var escaped_txt = "", kode + for (var i = 0; i < txt.length; i++) { + kode = txt.charCodeAt(i) + if (kode > 0x7f) { + kode = kode.toString(16) + switch (kode.length) { + case 2: + kode = "0" + kode + case 3: + kode = "0" + kode + } + escaped_txt += "\\u" + kode + } + else { + escaped_txt += txt[i] + } + } + return escaped_txt + } + + // encodes unicode characters as escaped bytes - \xFF + // encodes ONLY non-ascii characters + function escapeToEscapedBytes (txt) { + var escaped_txt = "", kode, utf8_bytes + for (var i = 0; i < txt.length; i++) { + kode = txt.charCodeAt(i) + if (kode > 0x7f) { + utf8_bytes = convertUnicodeCodePointToUtf8Bytes(kode) + escaped_txt += convertBytesToEscapedString(utf8_bytes, 16) + } + else { + escaped_txt += txt[i] + } + } + return escaped_txt + } + + // encodes unicode characters as escaped bytes - \xFF + // encodes an ENTIRE string + function escapeAllToEscapedBytes(str, base) { + var unicode_codes = convertStringToUnicodeCodePoints(str); + var data_bytes = convertUnicodeCodePointsToBytes(unicode_codes); + return convertBytesToEscapedString(data_bytes, 16); + } + // [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => '\xE3\x81\x82\xE3\x81\x84' + // [ 0343, 0201, 0202, 0343, 0201, 0204 ] => '\343\201\202\343\201\204' + function convertBytesToEscapedString(data_bytes, base) { + var escaped = ''; + for (var i = 0; i < data_bytes.length; ++i) { + var prefix = (base == 16 ? "\\x" : "\\"); + var num_digits = base == 16 ? 2 : 3; + var escaped_byte = prefix + formatNumber(data_bytes[i], base, num_digits) + escaped += escaped_byte; + } + return escaped; + } + // [ 0x3042, 0x3044 ] => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] + function convertUnicodeCodePointsToBytes(unicode_codes) { + var utf8_bytes = []; + for (var i = 0; i < unicode_codes.length; ++i) { + var bytes = convertUnicodeCodePointToUtf8Bytes(unicode_codes[i]); + utf8_bytes = utf8_bytes.concat(bytes); + } + return utf8_bytes; + } + // 0x3042 => [ 0xE3, 0x81, 0x82 ] + function convertUnicodeCodePointToUtf8Bytes(unicode_code) { + var utf8_bytes = []; + if (unicode_code < 0x80) { // 1-byte + utf8_bytes.push(unicode_code); + } else if (unicode_code < (1 << 11)) { // 2-byte + utf8_bytes.push((unicode_code >>> 6) | 0xC0); + utf8_bytes.push((unicode_code & 0x3F) | 0x80); + } else if (unicode_code < (1 << 16)) { // 3-byte + utf8_bytes.push((unicode_code >>> 12) | 0xE0); + utf8_bytes.push(((unicode_code >> 6) & 0x3f) | 0x80); + utf8_bytes.push((unicode_code & 0x3F) | 0x80); + } else if (unicode_code < (1 << 21)) { // 4-byte + utf8_bytes.push((unicode_code >>> 18) | 0xF0); + utf8_bytes.push(((unicode_code >> 12) & 0x3F) | 0x80); + utf8_bytes.push(((unicode_code >> 6) & 0x3F) | 0x80); + utf8_bytes.push((unicode_code & 0x3F) | 0x80); + } + return utf8_bytes; + } + // "あい" => [ 0x3042, 0x3044 ] + function convertStringToUnicodeCodePoints(str) { + var surrogate_1st = 0; + var unicode_codes = []; + for (var i = 0; i < str.length; ++i) { + var utf16_code = str.charCodeAt(i); + if (surrogate_1st != 0) { + if (utf16_code >= 0xDC00 && utf16_code <= 0xDFFF) { + var surrogate_2nd = utf16_code; + var unicode_code = (surrogate_1st - 0xD800) * (1 << 10) + (1 << 16) + + (surrogate_2nd - 0xDC00); + unicode_codes.push(unicode_code); + } else { + // Malformed surrogate pair ignored. + } + surrogate_1st = 0; + } else if (utf16_code >= 0xD800 && utf16_code <= 0xDBFF) { + surrogate_1st = utf16_code; + } else { + unicode_codes.push(utf16_code); + } + } + return unicode_codes; + } + // 0xff => "ff" + // 0xff => "377" + function formatNumber(number, base, num_digits) { + var str = number.toString(base).toUpperCase(); + for (var i = str.length; i < num_digits; ++i) { + str = "0" + str; + } + return str; + } + + // convert \xFF\xFF\xFF to unicode + function unescapeFromEscapedBytes (str) { + var data_bytes = convertEscapedBytesToBytes(str); + var unicode_codes = convertUtf8BytesToUnicodeCodePoints(data_bytes); + return convertUnicodeCodePointsToString(unicode_codes); + } + // r'\xE3\x81\x82\xE3\x81\x84' => [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] + // r'\343\201\202\343\201\204' => [ 0343, 0201, 0202, 0343, 0201, 0204 ] + function convertEscapedBytesToBytes(str) { + var parts = str.split("\\x"); + parts.shift(); // Trim the first element. + var codes = []; + var max = Math.pow(2, 8); + for (var i = 0; i < parts.length; ++i) { + var code = parseInt(parts[i], 16); + if (code >= 0 && code < max) { + codes.push(code); + } else { + // Malformed code ignored. + } + } + return codes; + } + // [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84 ] => [ 0x3042, 0x3044 ] + function convertUtf8BytesToUnicodeCodePoints(utf8_bytes) { + var unicode_codes = []; + var unicode_code = 0; + var num_followed = 0; + for (var i = 0; i < utf8_bytes.length; ++i) { + var utf8_byte = utf8_bytes[i]; + if (utf8_byte >= 0x100) { + // Malformed utf8 byte ignored. + } else if ((utf8_byte & 0xC0) == 0x80) { + if (num_followed > 0) { + unicode_code = (unicode_code << 6) | (utf8_byte & 0x3f); + num_followed -= 1; + } else { + // Malformed UTF-8 sequence ignored. + } + } else { + if (num_followed == 0) { + unicode_codes.push(unicode_code); + } else { + // Malformed UTF-8 sequence ignored. + } + if (utf8_byte < 0x80){ // 1-byte + unicode_code = utf8_byte; + num_followed = 0; + } else if ((utf8_byte & 0xE0) == 0xC0) { // 2-byte + unicode_code = utf8_byte & 0x1f; + num_followed = 1; + } else if ((utf8_byte & 0xF0) == 0xE0) { // 3-byte + unicode_code = utf8_byte & 0x0f; + num_followed = 2; + } else if ((utf8_byte & 0xF8) == 0xF0) { // 4-byte + unicode_code = utf8_byte & 0x07; + num_followed = 3; + } else { + // Malformed UTF-8 sequence ignored. + } + } + } + if (num_followed == 0) { + unicode_codes.push(unicode_code); + } else { + // Malformed UTF-8 sequence ignored. + } + unicode_codes.shift(); // Trim the first element. + return unicode_codes; + } + // [ 0x3042, 0x3044 ] => [ 0x3042, 0x3044 ] + // [ 0xD840, 0xDC0B ] => [ 0x2000B ] // A surrogate pair. + function convertUnicodeCodePointsToUtf16Codes(unicode_codes) { + var utf16_codes = []; + for (var i = 0; i < unicode_codes.length; ++i) { + var unicode_code = unicode_codes[i]; + if (unicode_code < (1 << 16)) { + utf16_codes.push(unicode_code); + } else { + var first = ((unicode_code - (1 << 16)) / (1 << 10)) + 0xD800; + var second = (unicode_code % (1 << 10)) + 0xDC00; + utf16_codes.push(first) + utf16_codes.push(second) + } + } + return utf16_codes; + } + // [ 0x3042, 0x3044 ] => "あい" + function convertUnicodeCodePointsToString(unicode_codes) { + var utf16_codes = convertUnicodeCodePointsToUtf16Codes(unicode_codes); + return convertUtf16CodesToString(utf16_codes); + } + // [ 0x3042, 0x3044 ] => "あい" + function convertUtf16CodesToString(utf16_codes) { + var unescaped = ''; + for (var i = 0; i < utf16_codes.length; ++i) { + unescaped += String.fromCharCode(utf16_codes[i]); + } + return unescaped; + } + + return { + raw: UNICODE_BLOCK_LIST, + lookup: UNICODE_LOOKUP, + index: index, + range: range, + block: block, + findGroups: findGroups, + paginate: paginate, + escapeToEscapedBytes: escapeToEscapedBytes, + unescapeFromEscapedBytes: unescapeFromEscapedBytes, + } +})() diff --git a/js/upload.js b/js/upload.js new file mode 100644 index 0000000..d60ae54 --- /dev/null +++ b/js/upload.js @@ -0,0 +1,83 @@ +var upload = (function(){ + var el = document.getElementById("upload_input") + var button = document.getElementById("upload_button") + var uploading = false + + function upload(blob, filename, tag, ascii){ + if (uploading) return + filename = filename || get_filename() + tag = tag || "shader" + + button.innerHTML = "uploading..." + button.className = "uploading" + + uploading = true + + uploadImage({ + blob: blob, + ascii: ascii, + filename: filename, + username: user.username, + tag: tag, + success: function(data){ + + // data.url + // data.filesize + // data.success + + console.log(data); + el.style.display = "block" + el.value = data.url + el.focus() + setCaretToPos(el, 0) + button.innerHTML = "upload" + button.className = "" + uploading = false + }, + error: function(data){ + console.log(data) + console.log("error uploading: " + data.error) + button.innerHTML = "upload" + button.className = "" + uploading = false + } + }); + } + + function uploadImage(opt){ + if (! opt.blob || ! opt.filename) return; + + opt.username = opt.username || ""; + opt.success = opt.success || noop; + opt.error = opt.error || noop; + + var form = new FormData(); + + form.append("username", opt.username); + form.append("filename", opt.filename); + form.append("qqfile", opt.blob); + form.append("tag", opt.tag); + if (opt.ascii) { + form.append("ascii", opt.ascii); + } + + var req = new XMLHttpRequest(); + req.open("POST", "/cgi-bin/im/shader/upload"); + req.onload = function(event) { + if (req.status == 200) { + var res = JSON.parse(req.responseText); + if (res.success) { + opt.success(res); + } + else { + opt.error(res); + } + } else { + opt.error({ success: false, error: req.status }); + } + }; + req.send(form); + } + + return upload +})() diff --git a/js/user.js b/js/user.js new file mode 100644 index 0000000..1546db9 --- /dev/null +++ b/js/user.js @@ -0,0 +1,67 @@ +var user = (function(){ + + var user = {} + var el = document.getElementById("username_input") + + user.init = function(){ + user.load() + user.bind() + } + user.bind = function(){ + el.addEventListener("input", user.save) + } + user.load = function(){ + user.username = user.getCookie() + if (! user.username) { + user.username = '00' + randint(9876876) + user.setCookie(user.username) + } + if (!user.username.match(/^00/)) { + el.value = user.username + } + } + user.prefs = new function(){} + user.prefs.get = function (key){ + return localStorage.getItem("im.prefs." + key) + } + user.prefs.set = function (key,value){ + return localStorage.setItem("im.prefs." + key, value) + } + user.sanitize = function(){ + return el.value.replace(/[^-_ a-zA-Z0-9]/g,"") + } + user.getCookie = function () { + var username = localStorage.getItem("im.name") || ""; + if (document.cookie && ! username.length) { + var cookies = document.cookie.split(";") + for (i in cookies) { + var cookie = cookies[i].split("=") + if (cookie[0].indexOf("imname") !== -1) { + if (cookie[1] !== 'false' && cookie[1] !== 'undefined' && cookie[1].length) { + return cookie[1] + } + } + } + } + return username + } + var timeout + user.save = function(){ + clearTimeout(timeout) + timeout = setTimeout(function(){ + var username = user.sanitize() + if (username != user.username) user.setCookie(username); + }) + } + user.setCookie = function(username){ + if (!user.username.match(/^00/)) { + console.log("setting to " + username) + } + document.cookie = "imname="+username+";path=/;domain=.asdf.us;max-age=1086400" + localStorage.setItem("im.name", username); + } + + user.init() + + return user +})() diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..ca97630 --- /dev/null +++ b/js/util.js @@ -0,0 +1,199 @@ +if (window.$) { + $.fn.int = function(){ return parseInt($(this).val(),10) } + $.fn.float = function(){ return parseFloat($(this).val()) } + $.fn.string = function(){ return trim($(this).val()) } + $.fn.enable = function() { return $(this).attr("disabled",null) } + $.fn.disable = function() { return $(this).attr("disabled","disabled") } +} + +function noop(){} +function trim(s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") } + +var E = Math.E +var PI = Math.PI +var PHI = (1+Math.sqrt(5))/2 +var TWO_PI = PI*2 +var LN10 = Math.LN10 +function clamp(n,a,b){ return n= 0.5 ? -1 : 1 } +function randnullsign(){ var r = random(); return r < 0.333 ? -1 : r < 0.666 ? 0 : 1 } + +function xrandom(exp){ return Math.pow(Math.random(), exp) } +function xrand(exp,n){ return (xrandom(exp)*n) } +function xrandint(exp,n){ return rand(exp,n)|0 } +function xrandrange(exp,a,b){ return a + xrand(exp,b-a) } + +function choice(a){ return a[randint(a.length)] } +function deg(n){ return n*180/PI } +function rad(n){ return n*PI/180 } +function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) } +function mod(n,m){ n = n % m; return n < 0 ? (m + n) : n } +function modi(n,m){ return floor(mod(n,m)) } +function dist(x0,y0,x1,y1){ return sqrt(pow(x1-x0,2)+pow(y1-y0,2)) } +function angle(x0,y0,x1,y1){ return atan2(y1-y0,x1-x0) } +function avg(m,n,a){ return (m*(a-1)+n)/a } +function quantize(a,b){ return floor(a/b)*b } +function quantile(a,b){ return floor(a/b) } + +function pixel(x,y){ return 4*(mod(y,actual_h)*actual_w+mod(x,actual_w)) } +function rgbpixel(d,x,y){ + var p = pixel(~~x,~~y) + r = d[p] + g = d[p+1] + b = d[p+2] + a = d[p+3] +} +function fit(d,x,y){ rgbpixel(d,x*actual_w/w,y*actual_h/h) } + +function step(a, b){ + return (b >= a) + 0 + // ^^ bool -> int +} + +function julestep (a,b,n) { + return clamp(norm(n,a,b), 0.0, 1.0); +} + +// hermite curve apparently +function smoothstep(min,max,n){ + var t = clamp((n - min) / (max - min), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t) +} + +function toArray(a){ return Array.prototype.slice.call(a) } +function shuffle(a){ + for (var i = a.length; i > 0; i--){ + var r = randint(i) + var swap = a[i-1] + a[i-1] = a[r] + a[r] = swap + } + return a +} +function reverse(a){ + var reversed = [] + for (var i = 0, _len = a.length-1; i <= _len; i++){ + reversed[i] = a[_len-i] + } + return reversed +} +function deinterlace(a){ + var odd = [], even = [] + for (var i = 0, _len = a.length; i < _len; i++) { + if (i % 2) even.push(a[i]) + else odd.push(a[i]) + } + return [even, odd] +} +function weave(a){ + var aa = deinterlace(a) + var b = [] + aa[0].forEach(function(el){ b.push(el) }) + reverse(aa[1]).forEach(function(el){ b.push(el) }) + return b +} +function cssRule (selector, declaration) { + var x = document.styleSheets, y = x.length-1; + x[y].insertRule(selector+"{"+declaration+"}", x[y].cssRules.length); +} + +// easing functions +function circular (t) { return Math.sqrt( 1 - ( --t * t ) ) } +function quadratic (t) { return t * ( 2 - t ) } +function back (t) { + var b = 4; + return ( t = t - 1 ) * t * ( ( b + 1 ) * t + b ) + 1; +} +function bounce (t) { + if (t >= 1) return 1; + if ( ( t /= 1 ) < ( 1 / 2.75 ) ) { + return 7.5625 * t * t; + } else if ( t < ( 2 / 2.75 ) ) { + return 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75; + } else if ( t < ( 2.5 / 2.75 ) ) { + return 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375; + } else { + return 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375; + } +} +function elastic (t) { + var f = 0.22, + e = 0.4; + + if ( t === 0 ) { return 0; } + if ( t == 1 ) { return 1; } + + return ( e * Math.pow( 2, - 10 * t ) * Math.sin( ( t - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 ); +} + +Model=function a(b,c,d,e){function f(){var a=this,f={};a.on=function(a,b){(f[a]|| +(f[a]=[])).push(b)},a.trigger=function(a,b){for(var c=f[a],d=0;c&&d-1;)d.splice(c +,1);f[a]=b?d:[]};for(c in b)d=b[c],a[c]=typeof d=="function"?function(){return( +d=this.apply(a,arguments))===e?a:d}.bind(d):d;a.init&&a.init.apply(a,arguments) +}return f.extend=function(f){d={};for(c in b)d[c]=b[c];for(c in f)d[c]=f[c],b[c +]!==e&&(d["__"+c]=b[c]);return a(d)},f},typeof module=="object"&&(module.exports +=Model); // c-{{{-< + +function defaults (dest, src) { + dest = dest || {} + for (var i in src) { + dest[i] = typeof dest[i] == 'undefined' ? src[i] : dest[i] + } + return dest +} + +function setSelectionRange(input, selectionStart, selectionEnd) { + if (input.setSelectionRange) { + input.focus(); + input.setSelectionRange(selectionStart, selectionEnd); + } + else if (input.createTextRange) { + var range = input.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + } +} +function setCaretToPos(input, pos) { + setSelectionRange(input, pos, pos); +} + +// Naive useragent detection pattern +var is_iphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) +var is_ipad = (navigator.userAgent.match(/iPad/i)) +var is_android = (navigator.userAgent.match(/Android/i)) +var is_mobile = is_iphone || is_ipad || is_android +var is_desktop = ! is_mobile; diff --git a/js/vendor/FileSaver.js b/js/vendor/FileSaver.js new file mode 100644 index 0000000..0f596ab --- /dev/null +++ b/js/vendor/FileSaver.js @@ -0,0 +1,232 @@ +/* FileSaver.js + * A saveAs() FileSaver implementation. + * 2013-10-21 + * + * By Eli Grey, http://eligrey.com + * License: X11/MIT + * See LICENSE.md + */ + +/*global self */ +/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, + plusplus: true */ + +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ + +var saveAs = saveAs + || (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator)) + || (function(view) { + "use strict"; + var + doc = view.document + // only get URL when necessary in case BlobBuilder.js hasn't overridden it yet + , get_URL = function() { + return view.URL || view.webkitURL || view; + } + , URL = view.URL || view.webkitURL || view + , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") + , can_use_save_link = !view.externalHost && "download" in save_link + , click = function(node) { + var event = doc.createEvent("MouseEvents"); + event.initMouseEvent( + "click", true, false, view, 0, 0, 0, 0, 0 + , false, false, false, false, 0, null + ); + node.dispatchEvent(event); + } + , webkit_req_fs = view.webkitRequestFileSystem + , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem + , throw_outside = function (ex) { + (view.setImmediate || view.setTimeout)(function() { + throw ex; + }, 0); + } + , force_saveable_type = "application/octet-stream" + , fs_min_size = 0 + , deletion_queue = [] + , process_deletion_queue = function() { + var i = deletion_queue.length; + while (i--) { + var file = deletion_queue[i]; + if (typeof file === "string") { // file is an object URL + URL.revokeObjectURL(file); + } else { // file is a File + file.remove(); + } + } + deletion_queue.length = 0; // clear queue + } + , dispatch = function(filesaver, event_types, event) { + event_types = [].concat(event_types); + var i = event_types.length; + while (i--) { + var listener = filesaver["on" + event_types[i]]; + if (typeof listener === "function") { + try { + listener.call(filesaver, event || filesaver); + } catch (ex) { + throw_outside(ex); + } + } + } + } + , FileSaver = function(blob, name) { + // First try a.download, then web filesystem, then object URLs + var + filesaver = this + , type = blob.type + , blob_changed = false + , object_url + , target_view + , get_object_url = function() { + var object_url = get_URL().createObjectURL(blob); + deletion_queue.push(object_url); + return object_url; + } + , dispatch_all = function() { + dispatch(filesaver, "writestart progress write writeend".split(" ")); + } + // on any filesys errors revert to saving with object URLs + , fs_error = function() { + // don't create more object URLs than needed + if (blob_changed || !object_url) { + object_url = get_object_url(blob); + } + if (target_view) { + target_view.location.href = object_url; + } else { + window.open(object_url, "_blank"); + } + filesaver.readyState = filesaver.DONE; + dispatch_all(); + } + , abortable = function(func) { + return function() { + if (filesaver.readyState !== filesaver.DONE) { + return func.apply(this, arguments); + } + }; + } + , create_if_not_found = {create: true, exclusive: false} + , slice + ; + filesaver.readyState = filesaver.INIT; + if (!name) { + name = "download"; + } + if (can_use_save_link) { + object_url = get_object_url(blob); + // FF for Android has a nasty garbage collection mechanism + // that turns all objects that are not pure javascript into 'deadObject' + // this means `doc` and `save_link` are unusable and need to be recreated + // `view` is usable though: + doc = view.document; + save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"); + save_link.href = object_url; + save_link.download = name; + var event = doc.createEvent("MouseEvents"); + event.initMouseEvent( + "click", true, false, view, 0, 0, 0, 0, 0 + , false, false, false, false, 0, null + ); + save_link.dispatchEvent(event); + filesaver.readyState = filesaver.DONE; + dispatch_all(); + return; + } + // Object and web filesystem URLs have a problem saving in Google Chrome when + // viewed in a tab, so I force save with application/octet-stream + // http://code.google.com/p/chromium/issues/detail?id=91158 + if (view.chrome && type && type !== force_saveable_type) { + slice = blob.slice || blob.webkitSlice; + blob = slice.call(blob, 0, blob.size, force_saveable_type); + blob_changed = true; + } + // Since I can't be sure that the guessed media type will trigger a download + // in WebKit, I append .download to the filename. + // https://bugs.webkit.org/show_bug.cgi?id=65440 + if (webkit_req_fs && name !== "download") { + name += ".download"; + } + if (type === force_saveable_type || webkit_req_fs) { + target_view = view; + } + if (!req_fs) { + fs_error(); + return; + } + fs_min_size += blob.size; + req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { + fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { + var save = function() { + dir.getFile(name, create_if_not_found, abortable(function(file) { + file.createWriter(abortable(function(writer) { + writer.onwriteend = function(event) { + target_view.location.href = file.toURL(); + deletion_queue.push(file); + filesaver.readyState = filesaver.DONE; + dispatch(filesaver, "writeend", event); + }; + writer.onerror = function() { + var error = writer.error; + if (error.code !== error.ABORT_ERR) { + fs_error(); + } + }; + "writestart progress write abort".split(" ").forEach(function(event) { + writer["on" + event] = filesaver["on" + event]; + }); + writer.write(blob); + filesaver.abort = function() { + writer.abort(); + filesaver.readyState = filesaver.DONE; + }; + filesaver.readyState = filesaver.WRITING; + }), fs_error); + }), fs_error); + }; + dir.getFile(name, {create: false}, abortable(function(file) { + // delete file if it already exists + file.remove(); + save(); + }), abortable(function(ex) { + if (ex.code === ex.NOT_FOUND_ERR) { + save(); + } else { + fs_error(); + } + })); + }), fs_error); + }), fs_error); + } + , FS_proto = FileSaver.prototype + , saveAs = function(blob, name) { + return new FileSaver(blob, name); + } + ; + FS_proto.abort = function() { + var filesaver = this; + filesaver.readyState = filesaver.DONE; + dispatch(filesaver, "abort"); + }; + FS_proto.readyState = FS_proto.INIT = 0; + FS_proto.WRITING = 1; + FS_proto.DONE = 2; + + FS_proto.error = + FS_proto.onwritestart = + FS_proto.onprogress = + FS_proto.onwrite = + FS_proto.onabort = + FS_proto.onerror = + FS_proto.onwriteend = + null; + + view.addEventListener("unload", process_deletion_queue, false); + return saveAs; +}(this.self || this.window || this.content)); +// `self` is undefined in Firefox for Android content script context +// while `this` is nsIContentFrameMessageManager +// with an attribute `content` that corresponds to the window + +if (typeof module !== 'undefined') module.exports = saveAs; \ No newline at end of file diff --git a/js/vendor/colorcode.js b/js/vendor/colorcode.js new file mode 100644 index 0000000..1d035d3 --- /dev/null +++ b/js/vendor/colorcode.js @@ -0,0 +1,551 @@ +!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),o.colorcode=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o cp437 function by sheetjs +// edited from https://github.com/SheetJS/js-codepage/blob/master/bits/437.js +var cp437 = (function(){ var d = "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ ", D = [], e = {}; for(var i=0;i!=d.length;++i) { if(d.charCodeAt(i) !== 0xFFFD) e[d[i]] = i; D[i] = d.charAt(i); } return {"enc": e, "dec": D }; })(); + +var render_char = function(font, char_value, ctx, ctx_x, ctx_y){ + char_value = cp437.enc[String.fromCharCode(char_value)] | 0; + var sheet_x = (char_value % font.sheet_w_in_chars) * font.char_w + var sheet_y = ((char_value / font.sheet_w_in_chars) |0) * font.char_h + ctx.drawImage(font.sheet, + sheet_x|0, sheet_y|0, font.char_w, font.char_h, + ctx_x|0, ctx_y|0, font.char_w, font.char_h) + +} + +for (var i=0, wh; wh=cp437s[i]; i++){ + var font = {}; + font.is_char_blank = require('../fontutil').is_char_blank; + font.render_char = render_char; + font.name = 'cp437_' + wh[0] + 'x' + wh[1]; + font.sheet_url = './img/' + font.name + '.png' + font.sheet_w_in_chars = 16; + font.char_w = wh[0] + font.char_h = wh[1] + fonts[font.name] = font; +} + + + +// window.cp437 = cp437; + +},{"../fontutil":7}],6:[function(require,module,exports){ +var fsexps = [[8,16,0],[8,15,1],[8,8,5]] +var fonts = {}; +module.exports = fonts; + +var render_char = function(font, char_value, ctx, ctx_x, ctx_y, char){ + var sheet_x = 0, sheet_y = 3; + if (char_value >= 0x20 && char_value <= 0x7e){ // ascii + sheet_x = (char_value - 0x20) * font.char_w_sheet + if (char.i){ // italic + sheet_y = 1 * font.char_h_sheet + 3 + } + } else if (char_value >= 0x80 && char_value <= 0xff){ // latin-1 + sheet_x = (char_value - 0x80) * font.char_w_sheet; + sheet_y = 2 * font.char_h_sheet + 3 + } else if (char_value >= 0x0100 && char_value <= 0x017f){ // latin a + sheet_x = (char_value - 0x0100) * font.char_w_sheet; + sheet_y = 3 * font.char_h_sheet + 3 + } else if (char_value >= 0x0180 && char_value <= 0x024f){ // latin b + sheet_x = (char_value - 0x0180) * font.char_w_sheet; + sheet_y = 4 * font.char_h_sheet + 3 + } else if (char_value >= 0x2500 && char_value <= 0x25ff){ // geom + sheet_x = (char_value - 0x2500) * font.char_w_sheet; + sheet_y = 5 * font.char_h_sheet + 3 + } else if (char_value >= 0x2600 && char_value <= 0x26ff){ // emoji + sheet_x = (char_value - 0x2600) * font.char_w_sheet; + sheet_y = 6 * font.char_h_sheet + 3 + } + + // var sheet_x = (char_value % font.sheet_w_in_chars) * font.char_w + // var sheet_y = ((char_value / font.sheet_w_in_chars) |0) * font.char_h + 3 + ctx.drawImage(font.sheet, + sheet_x|0, (sheet_y|0) + font.y_adj, font.char_w, font.char_h, + ctx_x|0, ctx_y|0, font.char_w, font.char_h) + +} + +for (var i=0, wh; wh=fsexps[i]; i++){ + var font = { + name: 'fixedsys_' + wh[0] + 'x' + wh[1], + sheet_url: './img/fsex-simple.png', + sheet_w_in_chars: 128, + char_w_sheet: 8, + char_h_sheet: 16, + char_w: wh[0], + char_h: wh[1], + y_adj: wh[2], + is_char_blank: require('../fontutil').is_char_blank, + render_char: render_char + } + fonts[font.name] = font +} +},{"../fontutil":7}],7:[function(require,module,exports){ +var util = {}; +module.exports = util; + +util.is_char_blank = function(char_value){ + if (char_value === 32) return true; +} + +util.render_char = function(font, char_value, ctx, ctx_x, ctx_y){ + var sheet_x = (char_value % font.sheet_w_in_chars) * font.char_w + var sheet_y = ((char_value / font.sheet_w_in_chars) |0) * font.char_h + ctx.drawImage(font.sheet, + sheet_x|0, sheet_y|0, font.char_w, font.char_h, + ctx_x|0, ctx_y|0, font.char_w, font.char_h) + +} + + +},{}],8:[function(require,module,exports){ +var char_color = '\x03'; + +var make_colorcode_fgbg = function(fg, bg){ + // pad numbers: this prevents irc parsing confusion + // when the character after the colorcode is a number + if (fg < 10) fg = "0" + fg; + if (bg < 10) bg = "0" + bg; + return char_color + fg + "," + bg +} + +var colorcode_from_json = function(json, opts){ + var out = ""; + for (var li=0, line; line=json.lines[li]; li++){ + for (var ci=0, char; char=line[ci]; ci++){ + out += make_colorcode_fgbg(char.fg, char.bg) + out += String.fromCharCode(char.value) + } + out += "\n"; + } + return out; +} + + +module.exports = colorcode_from_json; + +},{}],9:[function(require,module,exports){ +// default settings for fonts, colors, etc +var style = {}; +module.exports = style; + +},{}],10:[function(require,module,exports){ +var char_color = '\x03'; +var regexp_color = /(^[\d]{1,2})?(?:,([\d]{1,2}))?/; + +var style_chars = { + '\x02': 'bold', + '\x1d': 'italic', + '\x1f': 'underline', + '\x0f': 'reset', + '\x16': 'inverse' +}; + +var Style = function(style){ + this.b = style.b; + this.i = style.i; + this.u = style.u; + this.fg = style.fg; + this.bg = style.bg; +}; + +var style_fns = {}; + +style_fns.bold = function(style){ style.b = !style.b }; + +style_fns.italic = function(style){ style.i = !style.i }; + +style_fns.underline = function(style){ style.u = !style.u }; + +style_fns.inverse = function(style){ + var tmp = style.fg; + style.fg = style.bg; + style.bg = tmp; +}; + +style_fns.reset = function(style, base_style){ + style.b = base_style.b; + style.i = base_style.i; + style.u = base_style.u; + style.fg = base_style.fg; + style.bg = base_style.bg; +}; + +var colorcode_to_json = function(string, opts){ + // looks like its already converted + if (typeof string === 'object' && + 'lines' in string && + 'w' in string && + 'h' in string) + return string; + + + opts = opts || {}; + var d = colorcode_to_json.defaults; + + var base_style = { + b: "b" in opts ? opts.b : d.b, + i: "i" in opts ? opts.i : d.i, + u: "u" in opts ? opts.u : d.u, + fg: "fg" in opts ? opts.fg : d.fg, + bg: "bg" in opts ? opts.bg : d.bg + }; + + var lines_in = string.split(/\r?\n/); + var lines_out = []; + var w = 0, h = 0; + + for (var i=0; i 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (leadSurrogate) { + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } else { + // valid surrogate pair + codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000 + leadSurrogate = null + } + } else { + // no lead yet + + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else { + // valid lead + leadSurrogate = codePoint + continue + } + } + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = null + } + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x200000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function utf8Slice (buf, start, end) { + var res = '' + var tmp = '' + end = Math.min(buf.length, end || Infinity) + start = start || 0; + + for (var i = start; i < end; i++) { + if (buf[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]) + tmp = '' + } else { + tmp += '%' + buf[i].toString(16) + } + } + + return res + decodeUtf8Char(tmp) +} + +function decodeUtf8Char (str) { + try { + return decodeURIComponent(str) + } catch (err) { + return String.fromCharCode(0xFFFD) // UTF 8 invalid char + } +} + +TextEncoderLite.prototype.encode = function (str) { + var result; + + if ('undefined' === typeof Uint8Array) { + result = utf8ToBytes(str); + } else { + result = new Uint8Array(utf8ToBytes(str)); + } + + return result; +}; + +TextDecoderLite.prototype.decode = function (bytes) { + return utf8Slice(bytes, 0, bytes.length); +} + +}()); + +if (typeof TextEncoder === 'undefined') TextEncoder = TextEncoderLite +if (typeof TextDecoder === 'undefined') TextDecoder = TextDecoderLite