├── .gitignore ├── LICENSE ├── README.md ├── ansi.go ├── artworx.go ├── bin.go ├── cmd └── go-ansi │ └── main.go ├── drawchar.go ├── examples ├── 43-nslv1.ans ├── ICE-9605.IDF ├── PCBTEST.PCB ├── SF2_Avatars_by_Luciano_Ayres.ans ├── SQ-FORCE.IDF ├── TCFDESTINY.tnd ├── andyh-ansilove.ans ├── ave-love.ans ├── bym-ansilove.ans ├── cbn-ansilove.ans ├── cl!-al02.ans ├── cl!-al03.ans ├── cl!-al04.ans ├── dMG-ansilove.asc ├── gj-ansilove.ans ├── k1-alove.ans ├── ko-alove.ans ├── lu-ansilove.xb ├── no-alove.ans ├── ns-bp09.ans ├── om-ansilove.ans ├── plur-ansilove.txt ├── rad-love.ans ├── sk!n-ansilove.txt ├── spidy-ansilove.txt ├── spot-ansilove.txt ├── tcf-ansilove.xb ├── us-alov.ans ├── us-alove.asc ├── we-alove.ans └── yop-ANSilove.ans ├── fonts.go ├── goansi.go ├── icedraw.go ├── pcb.go ├── pngw.go ├── sauce.go ├── tundra.go └── xbin.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | go-ansi 4 | !go-ansi/ 5 | lock.json 6 | manifest.json 7 | vendor/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 ActiveState Software Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of ActiveState Software Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | go-ansi converts ANSi art files to PNG files. go-ansi is a Go port of [ansilove/C][1] that aims to have as few external dependencies as possible. It provides both a command-line application and a package interface to allow you to integrate it with your own applications. 4 | 5 | go-ansi supports all of the options from ansilove/C with the addition of 24bit ANSi support. 6 | 7 | # Installation 8 | 9 | After cloning this repo or downloading the code, you'll need to ensure that any required dependencies are installed. 10 | 11 | First, you will need a version of the Go programming language installed. You can of course download [ActiveGo](https://www.activestate.com/activego/downloads) for your preferred platform. 12 | 13 | This package uses [github.com/nfnt/resize](https://github.com/nfnt/resize). You can either manually place this within the 14 | `vendor` folder, or use a dependency management package. 15 | 16 | While it is still in development, we recommend using the `dep` tool. You can get it from [github.com/golang/dep](https://github.com/golang/dep) or via the Go toolchain: 17 | 18 | $ go get -u github.com/golang/dep/... 19 | 20 | After installing `dep` you can use it to fetch the dependencies automatically: 21 | 22 | $ dep init 23 | $ dep ensure -update 24 | 25 | # Features 26 | 27 | Rendering of all known ANSi / ASCII art file types: 28 | 29 | - ANSi (.ANS) 30 | - Binary (.BIN) 31 | - Artworx (.ADF) 32 | - iCE Draw (.IDF) 33 | - Xbin (.XB) [details](http://www.acid.org/info/xbin/xbin.htm) 34 | - PCBoard (.PCB) 35 | - Tundra (.TND) [details](https://sourceforge.net/projects/tundradraw/) 36 | - ASCII (.ASC) 37 | - Release info (.NFO) 38 | - Description in zipfile (.DIZ) 39 | 40 | Files with custom suffix default to the ANSi renderer (e.g. ICE or CIA). 41 | 42 | go-ansi is capabable of processing: 43 | 44 | - SAUCE records 45 | - DOS and Amiga fonts (embedded binary dump) 46 | - iCE colors 47 | 48 | Even more: 49 | 50 | - Output files are highly optimized 4-bit PNGs. 51 | - Optionally generates additional (and proper) Retina @2x PNG. 52 | - You can use custom options for adjusting output results. 53 | - Built-in support for rendering Amiga ASCII. 54 | - Support for 24-bit ANSi 55 | 56 | # Documentation 57 | 58 | ## Synopsis 59 | 60 | go-ansi [options] file 61 | go-ansi -e | -h | -v 62 | 63 | ## Options 64 | 65 | -b bits set to 9 to render 9th column of block characters (default: 8) 66 | -c columns adjust number of columns for BIN files (default: 160) 67 | -e print a list of examples 68 | -f font select font (default: 80x25) 69 | -h show help 70 | -i enable iCE colors 71 | -m mode set rendering mode for ANS files: 72 | ced black on gray, with 78 columns 73 | transparent render with transparent background 74 | workbench use Amiga Workbench palette 75 | -o file specify output filename/path 76 | -r creates additional Retina @2x output file 77 | -s show SAUCE record without generating output 78 | -v show version information 79 | 80 | There are certain cases where you need to set options for proper rendering. However, this is occasionally. Results turn out well with the built-in defaults. You may launch go-ansi with the option `-e` to get a list of basic examples. Note that columns is restricted to `BIN` and `TND` files, it won't affect other file types. 81 | 82 | ## Fonts 83 | 84 | go-ansi inherits all the embedded fonts from ansilove/C as binary data, so the most popular typefaces for rendering ANSi / ASCII art are available at your fingertips. 85 | 86 | PC fonts can be (all case-sensitive): 87 | 88 | - `80x25` (code page 437) 89 | - `80x50` (code page 437, 80x50 mode) 90 | - `baltic` (code page 775) 91 | - `cyrillic` (code page 855) 92 | - `french-canadian` (code page 863) 93 | - `greek` (code page 737) 94 | - `greek-869` (code page 869) 95 | - `hebrew` (code page 862) 96 | - `icelandic` (Code page 861) 97 | - `latin1` (code page 850) 98 | - `latin2` (code page 852) 99 | - `nordic` (code page 865) 100 | - `portuguese` (Code page 860) 101 | - `russian` (code page 866) 102 | - `terminus` (modern font, code page 437) 103 | - `turkish` (code page 857) 104 | 105 | AMIGA fonts can be (all case-sensitive): 106 | 107 | - `amiga` (alias to Topaz) 108 | - `microknight` (Original MicroKnight version) 109 | - `microknight+` (Modified MicroKnight version) 110 | - `mosoul` (Original mO'sOul font) 111 | - `pot-noodle` (Original P0T-NOoDLE font) 112 | - `topaz` (Original Topaz Kickstart 2.x version) 113 | - `topaz+` (Modified Topaz Kickstart 2.x+ version) 114 | - `topaz500` (Original Topaz Kickstart 1.x version) 115 | - `topaz500+` (Modified Topaz Kickstart 1.x version) 116 | 117 | ## Bits 118 | 119 | `bits` can be (all case-sensitive): 120 | 121 | - `8` (8-bit) 122 | - `9` (9-bit) 123 | 124 | Setting the bits to `9` will render the 9th column of block characters, so the output will look like it is displayed in real textmode. 125 | 126 | ## Rendering Mode 127 | 128 | `mode` can be (all case-sensitive): 129 | 130 | - `ced` 131 | - `transparent` 132 | - `workbench` 133 | 134 | Setting the mode to `ced` will cause the input file to be rendered in black on gray, and limit the output to 78 columns (only available for `ANS` files). Used together with an Amiga font, the output will look like it is displayed on Amiga. 135 | 136 | Setting the mode to `workbench` will cause the input file to be rendered using Amiga Workbench colors (only available for `ANS` files). 137 | 138 | Settings the mode to `transparent` will produce output files with transparent background (only available for `ANS` files). 139 | 140 | ## iCE Colors 141 | 142 | iCE colors are disabled by default, and can be enabled by specifying the `-i` option. 143 | 144 | When an ANSi source was created using iCE colors, it was done with a special mode where the blinking was disabled, and you had 16 background colors available. Basically, you had the same choice for background colors as for foreground colors, that's iCE colors. 145 | 146 | ## Columns 147 | 148 | `columns` is only relevant for .BIN files, and even for those files is optional. In most cases conversion will work fine if you don't set this flag, the default value is `160` then. So please pass `columns` only to `BIN` files and only if you exactly know what you're doing. 149 | 150 | ## SAUCE records 151 | 152 | You can use go-ansi as SAUCE reader without generating any output, just use option `-s` for this purpose. 153 | 154 | # License 155 | 156 | go-ansi is released under the BSD 3-Clause license. See `LICENSE` file for details. 157 | 158 | # Author 159 | 160 | go-ansi is developed by [Pete Garcin](http://rawktron.com). 161 | 162 | Based on [ansilove/C][1]. ansilove is developed by Stefan Vogt, Brian Cassidy, [Frederic Cambus](http://www.cambus.net). 163 | 164 | # Resources 165 | 166 | Project Homepage : [https://github.com/ActiveState/go-ansi](https://github.com/ActiveState/go-ansi) 167 | 168 | 169 | [1]: https://github.com/ByteProject/ansilove-C 170 | -------------------------------------------------------------------------------- /ansi.go: -------------------------------------------------------------------------------- 1 | // ansi.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "image" 20 | "image/color" 21 | "image/draw" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | // Character structure 27 | type ansiChar struct { 28 | positionX int 29 | positionY int 30 | colorBackground int 31 | colorForeground int 32 | colorFg24 color.RGBA 33 | colorBg24 color.RGBA 34 | currentChar byte 35 | bold bool 36 | italics bool 37 | underline bool 38 | } 39 | 40 | // Ansi takes an inputFileBuffer with .ans data and returns an image buffer 41 | func ansi(inputFileBuffer []byte, inputFileSize int64, fontName string, bits int, mode string, icecolors bool, fext string) image.Image { 42 | var f font 43 | 44 | columns := 80 45 | 46 | isDizFile := false 47 | ced := false 48 | transparent := false 49 | workbench := false 50 | 51 | // font selection 52 | alSelectFont(&f, fontName) 53 | 54 | // to deal with the bits flag, we declared handy bool types 55 | if mode == "ced" { 56 | ced = true 57 | } else if mode == "transparent" { 58 | transparent = true 59 | } else if mode == "workbench" { 60 | workbench = true 61 | } 62 | 63 | // check if current file has a .diz extension 64 | if fext == ".diz" { 65 | isDizFile = true 66 | } 67 | 68 | // image buffer 69 | var imANSi draw.Image 70 | 71 | // ANSi processing loops 72 | var loop int 73 | 74 | // character definitions 75 | var currentChar, nextChar, character byte 76 | var ansiSequenceChar byte 77 | 78 | // default color values 79 | colorBackground := 0 80 | colorForeground := 7 81 | 82 | var fgcolor color.RGBA 83 | var bgcolor color.RGBA 84 | 85 | // text attributes 86 | var bold, underline, italics, blink bool = false, false, false, false 87 | 88 | // positions 89 | var positionX, positionY, positionXMax, positionYMax int = 0, 0, 0, 0 90 | var savedPositionY, savedPositionX int = 0, 0 91 | 92 | // sequence parsing variables 93 | var seqArrayCount int 94 | var seqArray []string 95 | var seqGrab string 96 | 97 | // ANSi buffer structure array definition 98 | var structIndex int 99 | var ansiBuffer []ansiChar 100 | 101 | fg24 := color.RGBA{0, 0, 0, 0} 102 | bg24 := color.RGBA{0, 0, 0, 0} 103 | 104 | // ANSi interpreter 105 | for loop < int(inputFileSize)-1 { 106 | currentChar = inputFileBuffer[loop] 107 | nextChar = inputFileBuffer[loop+1] 108 | 109 | // TODO Make these properly scoped 110 | const wrapColumn80 bool = true 111 | if positionX == 80 && wrapColumn80 { 112 | positionY++ 113 | positionX = 0 114 | } 115 | 116 | // CR + LF 117 | if currentChar == 13 && nextChar == 10 { 118 | positionY++ 119 | positionX = 0 120 | loop++ 121 | } 122 | 123 | // LF 124 | if currentChar == 10 { 125 | positionY++ 126 | positionX = 0 127 | } 128 | 129 | // tab 130 | if currentChar == 9 { 131 | positionX += 8 132 | } 133 | 134 | // TODO Make this scoped properly 135 | const subBreak bool = true 136 | // sub 137 | if currentChar == 26 && subBreak { 138 | break 139 | } 140 | 141 | // ANSi sequence 142 | if currentChar == 27 && nextChar == 91 { 143 | for ansiSequenceLoop := 0; ansiSequenceLoop < 15; ansiSequenceLoop++ { 144 | ansiSequenceChar = inputFileBuffer[loop+2+ansiSequenceLoop] 145 | 146 | // cursor position 147 | if ansiSequenceChar == 'H' || ansiSequenceChar == 'f' { 148 | // create substring from the sequence's content 149 | 150 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 151 | 152 | // create sequence content array 153 | seqArray = strings.Split(seqGrab, ";") 154 | seqArrayCount = len(seqArray) 155 | 156 | if seqArrayCount > 1 { 157 | // convert grabbed sequence content to integers 158 | seqLine, _ := strconv.Atoi(seqArray[0]) 159 | seqColumn, _ := strconv.Atoi(seqArray[1]) 160 | 161 | // finally set the positions 162 | positionY = seqLine - 1 163 | positionX = seqColumn - 1 164 | } else { 165 | // no coordinates specified? we move to the home position 166 | positionY = 0 167 | positionX = 0 168 | } 169 | loop += ansiSequenceLoop + 2 170 | break 171 | } 172 | 173 | // cursor up 174 | if ansiSequenceChar == 'A' { 175 | // create substring from the sequence's content 176 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 177 | //println("A") 178 | 179 | // now get escape sequence's position value 180 | seqLine, _ := strconv.Atoi(seqGrab) 181 | 182 | if seqLine == 0 { 183 | seqLine = 1 184 | } 185 | 186 | positionY = positionY - seqLine 187 | 188 | loop += ansiSequenceLoop + 2 189 | break 190 | } 191 | 192 | // cursor down 193 | if ansiSequenceChar == 'B' { 194 | // create substring from the sequence's content 195 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 196 | //println("B") 197 | 198 | // now get escape sequence's position value 199 | seqLine, _ := strconv.Atoi(seqGrab) 200 | 201 | if seqLine == 0 { 202 | seqLine = 1 203 | } 204 | 205 | positionY = positionY + seqLine 206 | 207 | loop += ansiSequenceLoop + 2 208 | break 209 | } 210 | 211 | // cursor forward 212 | if ansiSequenceChar == 'C' { 213 | // create substring from the sequence's content 214 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 215 | //println("C") 216 | 217 | // now get escape sequence's position value 218 | seqColumn, _ := strconv.Atoi(seqGrab) 219 | 220 | if seqColumn == 0 { 221 | seqColumn = 1 222 | } 223 | 224 | positionX = positionX + seqColumn 225 | 226 | if positionX > 80 { 227 | positionX = 80 228 | } 229 | 230 | loop += ansiSequenceLoop + 2 231 | break 232 | } 233 | 234 | // cursor backward 235 | if ansiSequenceChar == 'D' { 236 | // create substring from the sequence's content 237 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 238 | //println("D") 239 | 240 | // now get escape sequence's content length 241 | seqColumn, _ := strconv.Atoi(seqGrab) 242 | 243 | if seqColumn == 0 { 244 | seqColumn = 1 245 | } 246 | 247 | positionX = positionX - seqColumn 248 | 249 | if positionX < 0 { 250 | positionX = 0 251 | } 252 | 253 | loop += ansiSequenceLoop + 2 254 | break 255 | } 256 | 257 | // save cursor position 258 | if ansiSequenceChar == 's' { 259 | savedPositionY = positionY 260 | savedPositionX = positionX 261 | //println("s") 262 | 263 | loop += ansiSequenceLoop + 2 264 | break 265 | } 266 | 267 | // restore cursor position 268 | if ansiSequenceChar == 'u' { 269 | positionY = savedPositionY 270 | positionX = savedPositionX 271 | //println("u") 272 | 273 | loop += ansiSequenceLoop + 2 274 | break 275 | } 276 | 277 | // erase display 278 | if ansiSequenceChar == 'J' { 279 | // create substring from the sequence's content 280 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 281 | //println("J") 282 | // convert grab to an integer 283 | eraseDisplayInt, _ := strconv.Atoi(seqGrab) 284 | 285 | if eraseDisplayInt == 2 { 286 | positionX = 0 287 | positionY = 0 288 | 289 | positionXMax = 0 290 | positionYMax = 0 291 | 292 | // reset ansi buffer 293 | ansiBuffer = nil 294 | structIndex = 0 295 | } 296 | loop += ansiSequenceLoop + 2 297 | break 298 | } 299 | 300 | // set graphics mode 301 | if ansiSequenceChar == 'm' { 302 | //println("m") 303 | // create substring from the sequence's content 304 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 305 | 306 | // create sequence content array 307 | seqArray = strings.Split(seqGrab, ";") 308 | seqArrayCount = len(seqArray) 309 | 310 | // fmt.Printf("SEQARRAY: %v", seqArray) 311 | // a loophole in limbo 312 | for seqGraphicsLoop := 0; seqGraphicsLoop < seqArrayCount; seqGraphicsLoop++ { 313 | // convert split content value to integer 314 | seqValue, _ := strconv.Atoi(seqArray[seqGraphicsLoop]) 315 | 316 | //println("SEQVALUE", seqValue) 317 | 318 | if seqValue == 0 { 319 | colorBackground = 0 320 | colorForeground = 7 321 | bold = false 322 | underline = false 323 | italics = false 324 | blink = false 325 | } 326 | 327 | if seqValue == 1 { 328 | if !workbench { 329 | colorForeground += 8 330 | } 331 | bold = true 332 | } 333 | 334 | if seqValue == 3 { 335 | italics = true 336 | } 337 | 338 | if seqValue == 4 { 339 | underline = true 340 | } 341 | 342 | if seqValue == 5 { 343 | if !workbench { 344 | colorBackground += 8 345 | } 346 | blink = true 347 | } 348 | 349 | if seqValue > 29 && seqValue < 38 { 350 | colorForeground = seqValue - 30 351 | 352 | if bold { 353 | colorForeground += 8 354 | } 355 | } 356 | 357 | if seqValue > 39 && seqValue < 48 { 358 | colorBackground = seqValue - 40 359 | 360 | if blink && icecolors { 361 | colorBackground += 8 362 | } 363 | } 364 | } 365 | 366 | loop += ansiSequenceLoop + 2 367 | break 368 | } 369 | 370 | // 24-bit ANSI support 371 | // Sets a temporary color for this sequence that overrides the foreground or background colors 372 | if ansiSequenceChar == 't' { 373 | // create substring from the sequence's content 374 | seqGrab = string(inputFileBuffer[loop+2 : loop+2+ansiSequenceLoop]) 375 | 376 | // create sequence content array 377 | seqArray = strings.Split(seqGrab, ";") 378 | seqArrayCount = len(seqArray) 379 | 380 | if seqArrayCount == 4 { 381 | r, _ := strconv.Atoi(seqArray[1]) 382 | g, _ := strconv.Atoi(seqArray[2]) 383 | b, _ := strconv.Atoi(seqArray[3]) 384 | 385 | if seqArray[0] == "0" { 386 | bg24 = color.RGBA{uint8(r), uint8(g), uint8(b), 255} 387 | } else if seqArray[0] == "1" { 388 | fg24 = color.RGBA{uint8(r), uint8(g), uint8(b), 255} 389 | } 390 | } 391 | 392 | loop += ansiSequenceLoop + 2 393 | break 394 | } 395 | 396 | // cursor (de)activation (Amiga ANSi) 397 | if ansiSequenceChar == 'p' { 398 | loop += ansiSequenceLoop + 2 399 | break 400 | } 401 | 402 | // skipping set mode and reset mode sequences 403 | if ansiSequenceChar == 'h' || ansiSequenceChar == 'l' { 404 | loop += ansiSequenceLoop + 2 405 | break 406 | } 407 | } 408 | } else if currentChar != 10 && currentChar != 13 && currentChar != 9 { 409 | // record number of columns and lines used 410 | if positionX > positionXMax { 411 | positionXMax = positionX 412 | } 413 | 414 | if positionY > positionYMax { 415 | positionYMax = positionY 416 | } 417 | 418 | // write current character in ansiChar structure 419 | if !f.isAmigaFont || (currentChar != 12 && currentChar != 13) { 420 | var newChar ansiChar 421 | 422 | newChar.colorBackground = colorBackground 423 | newChar.colorForeground = colorForeground 424 | newChar.colorFg24 = fg24 425 | newChar.colorBg24 = bg24 426 | newChar.currentChar = currentChar 427 | newChar.bold = bold 428 | newChar.italics = italics 429 | newChar.underline = underline 430 | newChar.positionX = positionX 431 | newChar.positionY = positionY 432 | 433 | ansiBuffer = append(ansiBuffer, newChar) 434 | 435 | fg24 = color.RGBA{0, 0, 0, 0} 436 | bg24 = color.RGBA{0, 0, 0, 0} 437 | 438 | structIndex++ 439 | positionX++ 440 | } 441 | } 442 | loop++ 443 | } 444 | 445 | // allocate image buffer memory 446 | positionXMax++ 447 | positionYMax++ 448 | 449 | if ced { 450 | columns = 78 451 | } 452 | 453 | if isDizFile { 454 | columns = min(positionXMax, 80) 455 | } 456 | 457 | imANSi = image.NewRGBA(image.Rect(0, 0, columns*bits, (positionYMax)*f.sizeY)) 458 | black := color.RGBA{0, 0, 0, 255} 459 | 460 | var colors [16]color.RGBA 461 | 462 | var cedBackground, cedForeground color.RGBA 463 | 464 | if ced { 465 | cedBackground = color.RGBA{170, 170, 170, 255} 466 | cedForeground = color.RGBA{0, 0, 0, 255} 467 | draw.Draw(imANSi, imANSi.Bounds(), &image.Uniform{cedBackground}, image.ZP, draw.Src) 468 | } else if workbench { 469 | 470 | if transparent { 471 | draw.Draw(imANSi, imANSi.Bounds(), image.Transparent, image.ZP, draw.Src) 472 | } else { 473 | draw.Draw(imANSi, imANSi.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 474 | } 475 | 476 | colors[0] = color.RGBA{170, 170, 170, 255} 477 | colors[1] = color.RGBA{0, 0, 0, 255} 478 | colors[2] = color.RGBA{255, 255, 255, 255} 479 | colors[3] = color.RGBA{102, 136, 187, 255} 480 | colors[4] = color.RGBA{0, 0, 255, 255} 481 | colors[5] = color.RGBA{255, 0, 255, 255} 482 | colors[6] = color.RGBA{0, 255, 255, 255} 483 | colors[7] = color.RGBA{255, 255, 255, 255} 484 | colors[8] = color.RGBA{170, 170, 170, 255} 485 | colors[9] = color.RGBA{0, 0, 0, 255} 486 | colors[10] = color.RGBA{255, 255, 255, 255} 487 | colors[11] = color.RGBA{102, 136, 187, 255} 488 | colors[12] = color.RGBA{0, 0, 255, 255} 489 | colors[13] = color.RGBA{255, 0, 255, 255} 490 | colors[14] = color.RGBA{0, 255, 255, 255} 491 | colors[15] = color.RGBA{255, 255, 255, 255} 492 | } else { 493 | // Allocate standard ANSi color palette 494 | if transparent { 495 | draw.Draw(imANSi, imANSi.Bounds(), image.Transparent, image.ZP, draw.Src) 496 | } else { 497 | draw.Draw(imANSi, imANSi.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 498 | } 499 | 500 | colors[0] = color.RGBA{0, 0, 0, 255} 501 | colors[1] = color.RGBA{170, 0, 0, 255} 502 | colors[2] = color.RGBA{0, 170, 0, 255} 503 | colors[3] = color.RGBA{170, 85, 0, 255} 504 | colors[4] = color.RGBA{0, 0, 170, 255} 505 | colors[5] = color.RGBA{170, 0, 170, 255} 506 | colors[6] = color.RGBA{0, 170, 170, 255} 507 | colors[7] = color.RGBA{170, 170, 170, 255} 508 | colors[8] = color.RGBA{85, 85, 85, 255} 509 | colors[9] = color.RGBA{255, 85, 85, 255} 510 | colors[10] = color.RGBA{85, 255, 85, 255} 511 | colors[11] = color.RGBA{255, 255, 85, 255} 512 | colors[12] = color.RGBA{85, 85, 255, 255} 513 | colors[13] = color.RGBA{255, 85, 255, 255} 514 | colors[14] = color.RGBA{85, 255, 255, 255} 515 | colors[15] = color.RGBA{255, 255, 255, 255} 516 | } 517 | 518 | // even more definitions, sigh 519 | ansiBufferItems := structIndex 520 | 521 | // render ANSi 522 | for loop := 0; loop < ansiBufferItems; loop++ { 523 | // grab ANSi char from our structure array 524 | colorBackground = ansiBuffer[loop].colorBackground 525 | colorForeground = ansiBuffer[loop].colorForeground 526 | character = ansiBuffer[loop].currentChar 527 | bold = ansiBuffer[loop].bold 528 | italics = ansiBuffer[loop].italics 529 | underline = ansiBuffer[loop].underline 530 | positionX = ansiBuffer[loop].positionX 531 | positionY = ansiBuffer[loop].positionY 532 | 533 | if ansiBuffer[loop].colorBg24.A > 0 { 534 | bgcolor = ansiBuffer[loop].colorBg24 535 | } else { 536 | bgcolor = colors[colorBackground] 537 | } 538 | if ansiBuffer[loop].colorFg24.A > 0 { 539 | fgcolor = ansiBuffer[loop].colorFg24 540 | } else { 541 | fgcolor = colors[colorForeground] 542 | } 543 | 544 | if ced { 545 | alDrawChar(imANSi, f.data, bits, f.sizeY, 546 | positionX, positionY, cedBackground, cedForeground, character) 547 | } else { 548 | alDrawChar(imANSi, f.data, bits, f.sizeY, 549 | positionX, positionY, bgcolor, fgcolor, character) 550 | } 551 | 552 | } 553 | 554 | return imANSi 555 | } 556 | 557 | func min(a, b int) int { 558 | if a < b { 559 | return a 560 | } 561 | return b 562 | } 563 | -------------------------------------------------------------------------------- /artworx.go: -------------------------------------------------------------------------------- 1 | // artworx.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "fmt" 20 | "image" 21 | "image/color" 22 | "image/draw" 23 | "os" 24 | ) 25 | 26 | // Artworx processes inputFileBuffer and generates an image 27 | func artworx(inputFileBuffer []byte, inputFileSize int64) image.Image { 28 | // some type declarations 29 | var f font 30 | 31 | // libgd image pointers 32 | var imADF draw.Image 33 | 34 | imADF = image.NewRGBA(image.Rect(0, 0, 640, (((int(inputFileSize)-192-4096-1)/2)/80)*16)) 35 | 36 | if imADF == nil { 37 | fmt.Printf("\nError, can't allocate buffer image memory.\n\n") 38 | os.Exit(6) 39 | } 40 | 41 | black := color.RGBA{0, 0, 0, 255} 42 | draw.Draw(imADF, imADF.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 43 | 44 | // ADF color palette array 45 | adfColors := [16]int{0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63} 46 | var colors [16]color.RGBA 47 | f.data = inputFileBuffer[193 : 193+4096] 48 | 49 | var loop int 50 | var index int 51 | // process ADF palette 52 | for loop = 0; loop < 16; loop++ { 53 | index = (adfColors[loop] * 3) + 1 54 | 55 | colors[loop] = color.RGBA{(inputFileBuffer[index]<<2 | inputFileBuffer[index]>>4), 56 | (inputFileBuffer[index+1]<<2 | inputFileBuffer[index+1]>>4), 57 | (inputFileBuffer[index+2]<<2 | inputFileBuffer[index+2]>>4), 255} 58 | } 59 | 60 | // process ADF 61 | var positionX, positionY int = 0, 0 62 | var character, attribute, colorForeground, colorBackground int 63 | loop = 192 + 4096 + 1 64 | 65 | for loop < int(inputFileSize) { 66 | if positionX == 80 { 67 | positionX = 0 68 | positionY++ 69 | } 70 | 71 | character = int(inputFileBuffer[loop]) 72 | attribute = int(inputFileBuffer[loop+1]) 73 | 74 | colorBackground = (attribute & 240) >> 4 75 | colorForeground = attribute & 15 76 | 77 | alDrawChar(imADF, f.data, 8, 16, positionX, positionY, colors[colorBackground], colors[colorForeground], byte(character)) 78 | 79 | positionX++ 80 | loop += 2 81 | } 82 | 83 | return imADF 84 | } 85 | -------------------------------------------------------------------------------- /bin.go: -------------------------------------------------------------------------------- 1 | // bin.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "fmt" 20 | "image" 21 | "image/color" 22 | "image/draw" 23 | "os" 24 | ) 25 | 26 | // binary processes inputFileBuffer and generates an image 27 | func binfile(inputFileBuffer []byte, inputFileSize int64, columns int, fontName string, bits int, icecolors bool) image.Image { 28 | // some type declarations 29 | var f font 30 | 31 | // font selection 32 | alSelectFont(&f, fontName) 33 | 34 | // libgd image pointers 35 | var imBinary draw.Image 36 | 37 | imBinary = image.NewRGBA(image.Rect(0, 0, columns*bits, (int(inputFileSize)/2)/columns*f.sizeY)) 38 | 39 | if imBinary == nil { 40 | fmt.Printf("\nError, can't allocate buffer image memory.\n\n") 41 | os.Exit(6) 42 | } 43 | 44 | black := color.RGBA{0, 0, 0, 255} 45 | draw.Draw(imBinary, imBinary.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 46 | 47 | // allocate color palette 48 | var colors [16]color.RGBA 49 | 50 | colors[0] = color.RGBA{0, 0, 0, 255} 51 | colors[1] = color.RGBA{0, 0, 170, 255} 52 | colors[2] = color.RGBA{0, 170, 0, 255} 53 | colors[3] = color.RGBA{0, 170, 170, 255} 54 | colors[4] = color.RGBA{170, 0, 0, 255} 55 | colors[5] = color.RGBA{170, 0, 170, 255} 56 | colors[6] = color.RGBA{170, 85, 0, 255} 57 | colors[7] = color.RGBA{170, 170, 170, 255} 58 | colors[8] = color.RGBA{85, 85, 85, 255} 59 | colors[9] = color.RGBA{85, 85, 255, 255} 60 | colors[10] = color.RGBA{85, 255, 85, 255} 61 | colors[11] = color.RGBA{85, 255, 255, 255} 62 | colors[12] = color.RGBA{255, 85, 85, 255} 63 | colors[13] = color.RGBA{255, 85, 255, 255} 64 | colors[14] = color.RGBA{255, 255, 85, 255} 65 | colors[15] = color.RGBA{255, 255, 255, 255} 66 | 67 | // process binary 68 | var character, attribute, colorBackground, colorForeground int 69 | var loop, positionX, positionY int = 0, 0, 0 70 | 71 | for loop < int(inputFileSize) { 72 | if positionX == columns { 73 | positionX = 0 74 | positionY++ 75 | } 76 | 77 | character = int(inputFileBuffer[loop]) 78 | attribute = int(inputFileBuffer[loop+1]) 79 | 80 | colorBackground = (attribute & 240) >> 4 81 | colorForeground = (attribute & 15) 82 | 83 | if colorBackground > 8 && !icecolors { 84 | colorBackground -= 8 85 | } 86 | 87 | alDrawChar(imBinary, f.data, bits, f.sizeY, 88 | positionX, positionY, colors[colorBackground], colors[colorForeground], byte(character)) 89 | 90 | positionX++ 91 | loop += 2 92 | } 93 | 94 | return imBinary 95 | } 96 | -------------------------------------------------------------------------------- /cmd/go-ansi/main.go: -------------------------------------------------------------------------------- 1 | // go-ansi 2 | // 3 | // Copyright (C) 2017 ActiveState Software Inc. 4 | // Written by Pete Garcin (@rawktron) 5 | // 6 | // Based on ansilove/C 7 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 8 | // All rights reserved. 9 | // ansilove/C is licensed under the BSD-2 License. 10 | // 11 | // go-ansi is licensed under the BSD 3-Clause License. 12 | // See the file LICENSE for details. 13 | // 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "image" 21 | "os" 22 | "path/filepath" 23 | "strings" 24 | 25 | goansi "github.com/ActiveState/go-ansi" 26 | ) 27 | 28 | // Version - Package Version 29 | const Version = "1.0.0" 30 | 31 | // ExitSuccess - 0 32 | const ExitSuccess = 0 33 | 34 | // ExitFailure - 1 35 | const ExitFailure = 1 36 | 37 | // Exported Constant Settings Values 38 | const ( 39 | SubBreak = true 40 | WrapCol80 = true 41 | ) 42 | 43 | func showHelp() { 44 | fmt.Print("\nSUPPORTED FILE TYPES:\n" + 45 | " ANS BIN ADF IDF XB PCB TND ASC NFO DIZ\n" + 46 | " Files with custom suffix default to the ANSi renderer.\n\n" + 47 | "PC FONTS:\n" + 48 | " 80x25 icelandic\n" + 49 | " 80x50 latin1\n" + 50 | " baltic latin2\n" + 51 | " cyrillic nordic\n" + 52 | " french-canadian portuguese\n" + 53 | " greek russian\n" + 54 | " greek-869 terminus\n" + 55 | " hebrew turkish\n\n" + 56 | "AMIGA FONTS:\n" + 57 | " amiga topaz\n" + 58 | " microknight topaz+\n" + 59 | " microknight+ topaz500\n" + 60 | " mosoul topaz500+\n" + 61 | " pot-noodle\n\n" + 62 | "DOCUMENTATION:\n" + 63 | " Detailed help is available at the go-ansi repository on GitHub.\n" + 64 | " \n\n") 65 | } 66 | 67 | func listExamples() { 68 | fmt.Print("\nEXAMPLES:\n") 69 | fmt.Print(" go-ansi file.ans (output path/name identical to input, no options)\n" + 70 | " go-ansi -i file.ans (enable iCE colors)\n" + 71 | " go-ansi -r file.ans (adds Retina @2x output file)\n" + 72 | " go-ansi -o dir/file file.ans (custom path/name for output)\n" + 73 | " go-ansi -s file.bin (just display SAUCE record, don't generate output)\n" + 74 | " go-ansi -m transparent file.ans (render with transparent background)\n" + 75 | " go-ansi -f amiga file.txt (custom font)\n" + 76 | " go-ansi -f 80x50 -b 9 -c 320 -i file.bin (custom font, bits, columns, icecolors)\n" + 77 | "\n") 78 | } 79 | 80 | func versionInfo() { 81 | fmt.Print("All rights reserved.\n" + 82 | "\nFork me on GitHub: \n" + 83 | "Bug reports: \n\n" + 84 | "This is free software, released under the 3-Clause BSD license.\n" + 85 | "\n\n") 86 | } 87 | 88 | // following the IEEE Std 1003.1 for utility conventions 89 | func synopsis() { 90 | fmt.Print("\nSYNOPSIS:\n" + 91 | " go-ansi [options] file\n" + 92 | " go-ansi -e | -h | -v\n\n" + 93 | "OPTIONS:\n" + 94 | " -b bits set to 9 to render 9th column of block characters (default: 8)\n" + 95 | " -c columns adjust number of columns for BIN files (default: 160)\n" + 96 | " -e print a list of examples\n" + 97 | " -f font select font (default: 80x25)\n" + 98 | " -h show help\n" + 99 | " -i enable iCE colors\n" + 100 | " -m mode set rendering mode for ANS files:\n" + 101 | " ced black on gray, with 78 columns\n" + 102 | " transparent render with transparent background\n" + 103 | " workbench use Amiga Workbench palette\n" + 104 | " -o file specify output filename/path\n" + 105 | " -r creates additional Retina @2x output file\n" + 106 | " -s show SAUCE record without generating output\n" + 107 | " -v show version information\n" + 108 | "\n") 109 | } 110 | 111 | // Simple error checking to reduce duplicated code 112 | func check(e error) { 113 | if e != nil { 114 | panic(e) 115 | } 116 | } 117 | 118 | func main() { 119 | fmt.Printf("go-ansi %s - ANSi / ASCII art to PNG converter\n"+ 120 | "Copyright (C) 2017 ActiveState Software Inc. Written by Pete Garcin.\n", Version) 121 | 122 | // SAUCE record related bool types 123 | justDisplaySAUCE := false 124 | fileHasSAUCE := false 125 | 126 | // retina output bool type 127 | createRetinaRep := false 128 | 129 | // iCE colors bool type 130 | icecolors := false 131 | 132 | // analyze options and do what has to be done 133 | fileIsBinary := false 134 | fileIsANSi := false 135 | fileIsPCBoard := false 136 | fileIsTundra := false 137 | 138 | var mode string 139 | var fontName string 140 | 141 | var input, output string 142 | var retinaout string 143 | 144 | var outputFile string 145 | // default to 8 if bits option is not specified 146 | bits := 8 147 | // default to 160 if columns option is not specified 148 | columns := 160 149 | 150 | // Define command line flags for parsing 151 | flag.IntVar(&bits, "b", 8, "-b bits") 152 | flag.IntVar(&columns, "c", 160, "-c columns") 153 | var exFl = flag.Bool("e", false, "-e show examples") 154 | flag.StringVar(&fontName, "f", "80x25", "-f font") 155 | var helpFl = flag.Bool("h", false, "-h show help") 156 | flag.BoolVar(&icecolors, "i", false, "-i enable iCE colors") 157 | flag.StringVar(&mode, "m", "", "-m mode") 158 | flag.StringVar(&output, "o", "", "-o file") 159 | flag.BoolVar(&createRetinaRep, "r", false, "-r") 160 | flag.BoolVar(&justDisplaySAUCE, "s", false, "-s") 161 | var verFl = flag.Bool("v", false, "-v") 162 | 163 | // Parse command line args 164 | flag.Parse() 165 | 166 | // Error checking on values 167 | if !(bits == 8 || bits == 9) { 168 | fmt.Print("\nInvalid value for bits.\n\n") 169 | os.Exit(ExitFailure) 170 | } 171 | 172 | if !(columns >= 1 && columns <= 8192) { 173 | fmt.Print("\nInvalid value for columns.\n\n") 174 | os.Exit(ExitFailure) 175 | } 176 | 177 | if *exFl { 178 | listExamples() 179 | os.Exit(ExitSuccess) 180 | } 181 | 182 | if *helpFl { 183 | showHelp() 184 | os.Exit(ExitSuccess) 185 | } 186 | 187 | if *verFl { 188 | versionInfo() 189 | os.Exit(ExitSuccess) 190 | } 191 | 192 | if len(flag.Args()) == 1 { 193 | input = flag.Arg(0) 194 | } else { 195 | synopsis() 196 | os.Exit(ExitSuccess) 197 | } 198 | 199 | // let's check the file for a valid SAUCE record 200 | record := goansi.GetSauce(input) 201 | 202 | // if we find a SAUCE record, update bool flag 203 | if string(record.Sauceinf.ID[:]) == goansi.SauceID { 204 | fileHasSAUCE = true 205 | } 206 | 207 | if !justDisplaySAUCE { 208 | // create output file name if output is not specified 209 | var outputName string 210 | 211 | if output == "" { 212 | outputName = input 213 | } else { 214 | outputName = output 215 | } 216 | 217 | // appending ".png" extension to output file name 218 | outputFile = outputName + ".png" 219 | 220 | if createRetinaRep { 221 | retinaout = outputName + "@2x.png" 222 | } 223 | 224 | // display name of input and output files 225 | fmt.Printf("\nInput File: %s\n", input) 226 | fmt.Printf("Output File: %s\n", outputFile) 227 | 228 | if createRetinaRep { 229 | fmt.Printf("Retina Output File: %s\n", retinaout) 230 | } 231 | 232 | // get file extension 233 | fext := strings.ToLower(filepath.Ext(input)) 234 | 235 | // Open File 236 | f, err := os.Open(input) 237 | check(err) 238 | // Get file size 239 | fi, err := f.Stat() 240 | check(err) 241 | inputFileSize := fi.Size() 242 | // Read File 243 | inputFileBuffer := make([]byte, inputFileSize) 244 | _, err = f.Read(inputFileBuffer) 245 | check(err) 246 | // close input file, we don't need it anymore 247 | f.Close() 248 | 249 | var outputImg image.Image 250 | // create the output file by invoking the appropiate function 251 | if fext == ".pcb" { 252 | fileIsPCBoard = true 253 | } else if fext == ".bin" { 254 | fileIsBinary = true 255 | } else if fext == ".tnd" { 256 | fileIsTundra = true 257 | } else { 258 | fileIsANSi = true 259 | } 260 | 261 | // CLI does image resizing inside the pngw pkg to avoid parsing the file twice 262 | outputImg = goansi.Parse(inputFileBuffer, inputFileSize, fontName, bits, columns, mode, icecolors, fext, 1.0) 263 | 264 | if outputImg != nil { 265 | goansi.WritePng(outputFile, outputImg, 1.0) 266 | if createRetinaRep { 267 | goansi.WritePng(retinaout, outputImg, 2.0) 268 | } 269 | } 270 | 271 | // gather information and report to the command line 272 | if fileIsANSi || fileIsBinary || 273 | fileIsPCBoard || fileIsTundra { 274 | fmt.Printf("Font: %s\n", fontName) 275 | fmt.Printf("Bits: %d\n", bits) 276 | } 277 | if icecolors && (fileIsANSi || fileIsBinary) { 278 | fmt.Printf("iCE Colors: enabled\n") 279 | } 280 | if fileIsBinary { 281 | fmt.Printf("Columns: %d\n", columns) 282 | } 283 | } 284 | // TODO SAUCE SUPPORT 285 | // either display SAUCE or tell us if there is no record 286 | if !fileHasSAUCE { 287 | fmt.Printf("\nFile %s does not have a SAUCE record.\n", input) 288 | } else { 289 | fmt.Printf("\nId: %s v%s\n", record.Sauceinf.ID, record.Sauceinf.Version) 290 | fmt.Printf("Title: %s\n", record.Sauceinf.Title) 291 | fmt.Printf("Author: %s\n", record.Sauceinf.Author) 292 | fmt.Printf("Group: %s\n", record.Sauceinf.Group) 293 | fmt.Printf("Date: %s\n", record.Sauceinf.Date) 294 | fmt.Printf("Datatype: %d\n", record.Sauceinf.DataType) 295 | fmt.Printf("Filetype: %d\n", record.Sauceinf.FileType) 296 | if record.Sauceinf.Flags != 0 { 297 | fmt.Printf("Flags: %d\n", record.Sauceinf.Flags) 298 | } 299 | if record.Sauceinf.Tinfo1 != 0 { 300 | fmt.Printf("Tinfo1: %d\n", record.Sauceinf.Tinfo1) 301 | } 302 | if record.Sauceinf.Tinfo2 != 0 { 303 | fmt.Printf("Tinfo2: %d\n", record.Sauceinf.Tinfo2) 304 | } 305 | if record.Sauceinf.Tinfo3 != 0 { 306 | fmt.Printf("Tinfo3: %d\n", record.Sauceinf.Tinfo3) 307 | } 308 | if record.Sauceinf.Tinfo4 != 0 { 309 | fmt.Printf("Tinfo4: %d\n", record.Sauceinf.Tinfo4) 310 | } 311 | 312 | fmt.Printf("Num comments: %d", record.Sauceinf.Comments) 313 | if record.Sauceinf.Comments > 0 && len(record.CommentLines) > 0 { 314 | fmt.Printf("Comments: ") 315 | for i := 0; i < int(record.Sauceinf.Comments); i++ { 316 | fmt.Printf("%s\n", record.CommentLines[i]) 317 | } 318 | } 319 | } 320 | 321 | os.Exit(ExitSuccess) 322 | } 323 | -------------------------------------------------------------------------------- /drawchar.go: -------------------------------------------------------------------------------- 1 | // drawchar.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "image" 20 | "image/color" 21 | "image/draw" 22 | ) 23 | 24 | // AlDrawChar - shared method for drawing ANSI characters into an image buffer 25 | func alDrawChar(im draw.Image, font []byte, bits int, fontSizeY int, positionX int, positionY int, colorBackground color.RGBA, colorForeground color.RGBA, character byte) { 26 | x := positionX * bits 27 | y := positionY * fontSizeY 28 | 29 | draw.Draw(im, image.Rect(x, y, x+bits, y+fontSizeY), &image.Uniform{colorBackground}, image.ZP, draw.Src) 30 | 31 | for line := 0; line < fontSizeY; line++ { 32 | for column := 0; column < bits; column++ { 33 | if (font[line+int(character)*fontSizeY] & (0x80 >> uint(column))) != 0 { 34 | im.Set(positionX*bits+column, positionY*fontSizeY+line, colorForeground) 35 | if bits == 9 && column == 7 && character > 191 && character < 224 { 36 | im.Set(positionX*bits+8, positionY*fontSizeY+line, colorForeground) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/43-nslv1.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/43-nslv1.ans -------------------------------------------------------------------------------- /examples/ICE-9605.IDF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/ICE-9605.IDF -------------------------------------------------------------------------------- /examples/PCBTEST.PCB: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/PCBTEST.PCB -------------------------------------------------------------------------------- /examples/SF2_Avatars_by_Luciano_Ayres.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/SF2_Avatars_by_Luciano_Ayres.ans -------------------------------------------------------------------------------- /examples/SQ-FORCE.IDF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/SQ-FORCE.IDF -------------------------------------------------------------------------------- /examples/TCFDESTINY.tnd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/TCFDESTINY.tnd -------------------------------------------------------------------------------- /examples/andyh-ansilove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/andyh-ansilove.ans -------------------------------------------------------------------------------- /examples/ave-love.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/ave-love.ans -------------------------------------------------------------------------------- /examples/bym-ansilove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/bym-ansilove.ans -------------------------------------------------------------------------------- /examples/cbn-ansilove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/cbn-ansilove.ans -------------------------------------------------------------------------------- /examples/cl!-al02.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/cl!-al02.ans -------------------------------------------------------------------------------- /examples/cl!-al03.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/cl!-al03.ans -------------------------------------------------------------------------------- /examples/cl!-al04.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/cl!-al04.ans -------------------------------------------------------------------------------- /examples/dMG-ansilove.asc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/dMG-ansilove.asc -------------------------------------------------------------------------------- /examples/gj-ansilove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/gj-ansilove.ans -------------------------------------------------------------------------------- /examples/k1-alove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/k1-alove.ans -------------------------------------------------------------------------------- /examples/ko-alove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/ko-alove.ans -------------------------------------------------------------------------------- /examples/lu-ansilove.xb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/lu-ansilove.xb -------------------------------------------------------------------------------- /examples/no-alove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/no-alove.ans -------------------------------------------------------------------------------- /examples/ns-bp09.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/ns-bp09.ans -------------------------------------------------------------------------------- /examples/om-ansilove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/om-ansilove.ans -------------------------------------------------------------------------------- /examples/plur-ansilove.txt: -------------------------------------------------------------------------------- 1 | ____ __ __ ___. __ __ _______._______ 2 | __\ \./ \| \. \ |_| \| |_/ \ | / _/ 3 | \___\__|__|\___|_____/___|____/____/\___/\____/plur 4 | 5 | -- 6 | ____ __ __ ___. __ 7 | __\ \./ \| \. \ |_| \. --- - - ---÷plur÷- -. 8 | \___\__|__|\___|_____/___| : 9 | __ _______._______ 10 | : ansilove.sourceforge.net | |_/ \ | / _/ 11 | `---- -- -÷- - - ---- --|____/____/\___/\____/ 12 | -------------------------------------------------------------------------------- /examples/rad-love.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/rad-love.ans -------------------------------------------------------------------------------- /examples/sk!n-ansilove.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/sk!n-ansilove.txt -------------------------------------------------------------------------------- /examples/spidy-ansilove.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | a N S I L O V E ::. 4 | ::. 5 | 6 | -------------:________---|-----------|------|--------|----/_--:_---|------- 7 | --------|\\\\| /_--: spidy /_-: /---: /_-: | /_-: /_ 8 | ---\ /_----:_--- |_--- |--- |_-- |_- | : | 9 | ::---------- |---/_-\\::---/_\\::::--/_\\::--/_\\::-/_\\::--- |-- : 10 | -----------/_-\\::::--------::----------|-------:-------:------|--/_\\:-/_\\: 11 | 12 | 13 | 14 | 15 | 16 | a N S I L O V E 17 | ::. _------ __|\\|--- 18 | :------- ::. _----| /_-----: /-:------ 19 | ------||-------| /_--:_-|\\\|--| /_--:_-|\\|-- |_\\|--- |_\--- 20 | |\\\--- /_-: /_-: | spidy /_-: | /_-: /_--: /- 21 | --\ /_--: |_----- |_-- |_-- |_--- |_-- |-- : 22 | ::-------::|---------:-----/--:--/_\\\|::-:--/_\::---/_\\|:::--/_\\\|:::-/_\\: 23 | -----------------/_--------\:---------------|-------------:----------:------ 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/spot-ansilove.txt: -------------------------------------------------------------------------------- 1 | logo says Ansilove, requested by Cleaner 2 | 3 | 4 | O 5 | o o 6 | 7 | . . 8 | ____ ___ ____ _____ ___ ___ ____ ____ _______ 9 | ___\\_ _\\ \/ /_\_ _/_ \\_// /___\_ _\\\_/ _/_\ ___/// 10 | \_ _ \_ \ /_ / // / / / / \_ / /\_ __/_ 11 | ///______/__/____\/______\ __/______\____//_____\ /_______\\spot 12 | . 13 | 14 | 15 | o . 16 | 17 | o o 18 | O 19 | -------------------------------------------------------------------------------- /examples/tcf-ansilove.xb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/tcf-ansilove.xb -------------------------------------------------------------------------------- /examples/us-alov.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/us-alov.ans -------------------------------------------------------------------------------- /examples/us-alove.asc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/us-alove.asc -------------------------------------------------------------------------------- /examples/we-alove.ans: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveState/go-ansi/e95170f796af14a16fefa2e9234b2fcd16f6d14f/examples/we-alove.ans -------------------------------------------------------------------------------- /examples/yop-ANSilove.ans: -------------------------------------------------------------------------------- 1 |  2 | _ __ ____ ___________ _________________ _ 3 | .________ //_ ANSilOVE _\\ ._____ 4 | ____/\ ___| _/___/\ __ _____. _ ____/\ _| \ ____ 5 | / .__\\ \ | _ //___ _)_. \_ |____ \\ | // ___/ 6 | / __/ \ \ |__ \/ \ |_ | | \ | \ / __)____ 7 | /____| \_|\_____|_________/__ `/ |______ //_______/____/______ / 8 | |______/ _ __ __________ \/ _______ \/ __________ _ yop \/ 9 | -------------------------------------------------------------------------------- /goansi.go: -------------------------------------------------------------------------------- 1 | // package goansi provides library-level access to ANSi file formats and SAUCE records 2 | package goansi 3 | 4 | import ( 5 | "bytes" 6 | "image" 7 | 8 | "github.com/nfnt/resize" 9 | ) 10 | 11 | // Parse takes a buffer of ANSi data and returns an Image.image 12 | func Parse(inputFileBuffer []byte, inputFileSize int64, fontName string, bits int, columns int, mode string, icecolors bool, fext string, scaleFactor float32) image.Image { 13 | 14 | var outputImg image.Image 15 | adjustedSize := inputFileSize 16 | buf := bytes.NewReader(inputFileBuffer) 17 | record := readRecord(buf) 18 | 19 | // if we find a SAUCE record, update bool flag 20 | fileHasSAUCE := (record != nil && string(record.Sauceinf.ID[:]) == SauceID) 21 | 22 | // adjust the file size if file contains a SAUCE record 23 | if fileHasSAUCE { 24 | adjustedSize -= 129 25 | if record.Sauceinf.Comments > 0 { 26 | adjustedSize -= int64(5 + 64*record.Sauceinf.Comments) 27 | } 28 | } 29 | 30 | // create the output file by invoking the appropiate function 31 | if fext == ".pcb" { 32 | // params: input, output, font, bits 33 | outputImg = pcboard(inputFileBuffer, adjustedSize, fontName, bits, icecolors) 34 | } else if fext == ".bin" { 35 | // params: input, output, columns, font, bits, icecolors 36 | outputImg = binfile(inputFileBuffer, adjustedSize, columns, fontName, bits, icecolors) 37 | } else if fext == ".adf" { 38 | // params: input, output, bits 39 | outputImg = artworx(inputFileBuffer, adjustedSize) 40 | } else if fext == ".idf" { 41 | // params: input, output, bits 42 | outputImg = icedraw(inputFileBuffer, adjustedSize) 43 | } else if fext == ".tnd" { 44 | outputImg = tundra(inputFileBuffer, adjustedSize, columns, fontName, bits) 45 | } else if fext == ".xb" { 46 | // params: input, output, bits 47 | outputImg = xbin(inputFileBuffer, adjustedSize) 48 | } else { 49 | // params: input, output, font, bits, icecolors, fext 50 | outputImg = ansi(inputFileBuffer, adjustedSize, fontName, bits, mode, icecolors, fext) 51 | } 52 | 53 | if scaleFactor != 1.0 && outputImg != nil { 54 | scaledHeight := float32(outputImg.Bounds().Max.Y) * scaleFactor 55 | scaledWidth := float32(outputImg.Bounds().Max.X) * scaleFactor 56 | outputImg = resize.Resize(uint(scaledWidth), uint(scaledHeight), outputImg, resize.NearestNeighbor) 57 | } 58 | 59 | return outputImg 60 | } 61 | 62 | // GetSauce returns a sauce record for a given file if it exists 63 | func GetSauce(fileName string) Sauce { 64 | return *readFileName(fileName) 65 | } 66 | -------------------------------------------------------------------------------- /icedraw.go: -------------------------------------------------------------------------------- 1 | // icedraw.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "encoding/binary" 20 | "fmt" 21 | "image" 22 | "image/color" 23 | "image/draw" 24 | "os" 25 | ) 26 | 27 | func icedraw(inputFileBuffer []byte, inputFileSize int64) image.Image { 28 | // extract relevant part of the IDF header, 16-bit little-endian unsigned short 29 | var byteBuf = []byte{inputFileBuffer[8], inputFileBuffer[9]} 30 | x2 := binary.LittleEndian.Uint16(byteBuf) 31 | 32 | // libgd image pointers 33 | var imIDF draw.Image 34 | 35 | var loop int 36 | var index int 37 | var colors [16]color.RGBA 38 | 39 | offset := inputFileSize - 48 - 4096 40 | 41 | fontData := inputFileBuffer[offset : offset+4096] 42 | 43 | // process IDF 44 | loop = 12 45 | var idfSequenceLength, idfSequenceLoop int 46 | 47 | // dynamically allocated memory buffer for IDF data 48 | var idfBuffer []byte 49 | 50 | var idfData, idfDataLength int16 51 | 52 | for loop < (int(inputFileSize) - 4096 - 48) { 53 | var byteBuf = []byte{inputFileBuffer[loop], inputFileBuffer[loop+1]} 54 | idfData = int16(binary.LittleEndian.Uint16(byteBuf)) 55 | 56 | // RLE compressed data 57 | if idfData == 1 { 58 | var byteBuf = []byte{inputFileBuffer[loop+2], inputFileBuffer[loop+3]} 59 | idfDataLength = int16(binary.LittleEndian.Uint16(byteBuf)) 60 | idfSequenceLength = int(idfDataLength & 255) 61 | 62 | for idfSequenceLoop = 0; idfSequenceLoop < idfSequenceLength; idfSequenceLoop++ { 63 | idfBuffer = append(idfBuffer, inputFileBuffer[loop+4]) 64 | idfBuffer = append(idfBuffer, inputFileBuffer[loop+5]) 65 | } 66 | loop += 4 67 | } else { 68 | // normal character 69 | idfBuffer = append(idfBuffer, inputFileBuffer[loop]) 70 | idfBuffer = append(idfBuffer, inputFileBuffer[loop+1]) 71 | } 72 | loop += 2 73 | } 74 | 75 | // create IDF instance 76 | imIDF = image.NewRGBA(image.Rect(0, 0, int((x2+1)*8), len(idfBuffer)/2/80*16)) 77 | 78 | if imIDF == nil { 79 | fmt.Printf("\nError, can't allocate buffer image memory.\n\n") 80 | os.Exit(6) 81 | } 82 | 83 | black := color.RGBA{0, 0, 0, 255} 84 | draw.Draw(imIDF, imIDF.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 85 | 86 | // process IDF palette 87 | for loop = 0; loop < 16; loop++ { 88 | index = (loop * 3) + int(inputFileSize) - 48 89 | r := (inputFileBuffer[index]<<2 | inputFileBuffer[index]>>4) 90 | g := (inputFileBuffer[index+1]<<2 | inputFileBuffer[index+1]>>4) 91 | b := (inputFileBuffer[index+2]<<2 | inputFileBuffer[index+2]>>4) 92 | colors[loop] = color.RGBA{r, g, b, 255} 93 | } 94 | 95 | // render IDF 96 | var positionX, positionY int 97 | var character, attribute, colorForeground, colorBackground int 98 | 99 | for loop = 0; loop < len(idfBuffer); loop += 2 { 100 | if positionX == int(x2+1) { 101 | positionX = 0 102 | positionY++ 103 | } 104 | 105 | character = int(idfBuffer[loop]) 106 | attribute = int(idfBuffer[loop+1]) 107 | 108 | colorBackground = (attribute & 240) >> 4 109 | colorForeground = attribute & 15 110 | 111 | alDrawChar(imIDF, fontData, 8, 16, positionX, positionY, colors[colorBackground], colors[colorForeground], byte(character)) 112 | 113 | positionX++ 114 | } 115 | 116 | // return IDF image 117 | return imIDF 118 | } 119 | -------------------------------------------------------------------------------- /pcb.go: -------------------------------------------------------------------------------- 1 | // pcb.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "fmt" 20 | "image" 21 | "image/color" 22 | "image/draw" 23 | "os" 24 | ) 25 | 26 | // Character structure 27 | type pcbChar struct { 28 | positionX int 29 | positionY int 30 | colorBackground int 31 | colorForeground int 32 | currentChar int 33 | } 34 | 35 | func pcboard(inputFileBuffer []byte, inputFileSize int64, fontName string, bits int, icecolors bool) image.Image { 36 | // some type declarations 37 | var f font 38 | columns := 80 39 | var loop int 40 | 41 | // font selection 42 | alSelectFont(&f, fontName) 43 | 44 | // libgd image pointers 45 | var imPCB draw.Image 46 | 47 | // process PCBoard 48 | var char, currentChar, nextChar int 49 | var colorBackground, colorForeground int = 0, 7 50 | var posX, posY, posXMax, posYMax int 51 | 52 | // PCB buffer structure array definition 53 | var pcbBuffer []pcbChar 54 | 55 | // reset loop 56 | loop = 0 57 | structIndex := 0 58 | 59 | for loop < int(inputFileSize) { 60 | currentChar = int(inputFileBuffer[loop]) 61 | nextChar = int(inputFileBuffer[loop+1]) 62 | 63 | if posX == 80 { 64 | posY++ 65 | posX = 0 66 | } 67 | 68 | // CR + LF 69 | if currentChar == 13 && nextChar == 10 { 70 | posY++ 71 | posX = 0 72 | loop++ 73 | } 74 | 75 | // LF 76 | if currentChar == 10 { 77 | posY++ 78 | posX = 0 79 | } 80 | 81 | // Tab 82 | if currentChar == 9 { 83 | posX += 8 84 | } 85 | 86 | // Sub 87 | if currentChar == 26 { 88 | break 89 | } 90 | 91 | // PCB sequence 92 | if currentChar == 64 && nextChar == 88 { 93 | colorBackground = int(inputFileBuffer[loop+2]) 94 | if colorBackground >= 65 { 95 | colorBackground -= 55 96 | } else { 97 | colorBackground -= 48 98 | } 99 | if !icecolors && colorBackground > 7 { 100 | colorBackground -= 8 101 | } 102 | colorForeground = int(inputFileBuffer[loop+3]) 103 | if colorForeground >= 65 { 104 | colorForeground -= 55 105 | } else { 106 | colorForeground -= 48 107 | } 108 | loop += 3 109 | } else if currentChar == 64 && nextChar == 67 && 110 | inputFileBuffer[loop+2] == 'L' && inputFileBuffer[loop+3] == 'S' { 111 | // erase display 112 | posX = 0 113 | posY = 0 114 | 115 | posXMax = 0 116 | posYMax = 0 117 | 118 | loop += 4 119 | } else if currentChar == 64 && nextChar == 80 && inputFileBuffer[loop+2] == 'O' && inputFileBuffer[loop+3] == 'S' && inputFileBuffer[loop+4] == ':' { 120 | // cursor position 121 | if inputFileBuffer[loop+6] == '@' { 122 | posX = int(((inputFileBuffer[loop+5]) - 48)) - 1 123 | loop += 5 124 | } else { 125 | posX = int((10*((inputFileBuffer[loop+5])-48) + (inputFileBuffer[loop+6]) - 48)) - 1 126 | loop += 6 127 | } 128 | } else if currentChar != 10 && currentChar != 13 && currentChar != 9 { 129 | // record number of columns and lines used 130 | if posX > posXMax { 131 | posXMax = posX 132 | } 133 | 134 | if posY > posYMax { 135 | posYMax = posY 136 | } 137 | 138 | var newChar pcbChar 139 | 140 | // write current character in pcbChar structure 141 | newChar.positionX = posX 142 | newChar.positionY = posY 143 | newChar.colorBackground = colorBackground 144 | newChar.colorForeground = colorForeground 145 | newChar.currentChar = currentChar 146 | 147 | pcbBuffer = append(pcbBuffer, newChar) 148 | 149 | structIndex++ 150 | posX++ 151 | } 152 | loop++ 153 | } 154 | posXMax++ 155 | posYMax++ 156 | 157 | // allocate color palette 158 | var colors [16]color.RGBA 159 | 160 | colors[0] = color.RGBA{0, 0, 0, 255} 161 | colors[1] = color.RGBA{0, 0, 170, 255} 162 | colors[2] = color.RGBA{0, 170, 0, 255} 163 | colors[3] = color.RGBA{0, 170, 170, 255} 164 | colors[4] = color.RGBA{170, 0, 0, 255} 165 | colors[5] = color.RGBA{170, 0, 170, 255} 166 | colors[6] = color.RGBA{170, 85, 0, 255} 167 | colors[7] = color.RGBA{170, 170, 170, 255} 168 | colors[8] = color.RGBA{85, 85, 85, 255} 169 | colors[9] = color.RGBA{85, 85, 255, 255} 170 | colors[10] = color.RGBA{85, 255, 85, 255} 171 | colors[11] = color.RGBA{85, 255, 255, 255} 172 | colors[12] = color.RGBA{255, 85, 85, 255} 173 | colors[13] = color.RGBA{255, 85, 255, 255} 174 | colors[14] = color.RGBA{255, 255, 85, 255} 175 | colors[15] = color.RGBA{255, 255, 255, 255} 176 | 177 | imPCB = image.NewRGBA(image.Rect(0, 0, columns*bits, posYMax*f.sizeY)) 178 | 179 | if imPCB == nil { 180 | fmt.Printf("\nError, can't allocate buffer image memory.\n\n") 181 | os.Exit(6) 182 | } 183 | 184 | black := color.RGBA{0, 0, 0, 255} 185 | draw.Draw(imPCB, imPCB.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 186 | 187 | // render PCB 188 | for loop = 0; loop < structIndex; loop++ { 189 | // grab our chars out of the structure 190 | posX = pcbBuffer[loop].positionX 191 | posY = pcbBuffer[loop].positionY 192 | colorBackground = pcbBuffer[loop].colorBackground 193 | colorForeground = pcbBuffer[loop].colorForeground 194 | char = pcbBuffer[loop].currentChar 195 | 196 | alDrawChar(imPCB, f.data, bits, f.sizeY, posX, posY, colors[colorBackground], colors[colorForeground], byte(char)) 197 | } 198 | 199 | return imPCB 200 | } 201 | -------------------------------------------------------------------------------- /pngw.go: -------------------------------------------------------------------------------- 1 | // pngw.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "image" 20 | "image/png" 21 | "log" 22 | "os" 23 | 24 | "github.com/nfnt/resize" 25 | ) 26 | 27 | // WritePng takes an image and filename and encodes to png 28 | func WritePng(fileName string, img image.Image, scaleFactor float32) { 29 | 30 | scaledImg := img 31 | 32 | if scaleFactor != 1.0 { 33 | scaledHeight := float32(img.Bounds().Max.Y) * scaleFactor 34 | scaledWidth := float32(img.Bounds().Max.X) * scaleFactor 35 | scaledImg = resize.Resize(uint(scaledWidth), uint(scaledHeight), img, resize.NearestNeighbor) 36 | } 37 | 38 | // create output file 39 | f, err := os.Create(fileName) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | if err := png.Encode(f, scaledImg); err != nil { 45 | f.Close() 46 | log.Fatal(err) 47 | } 48 | 49 | if err := f.Close(); err != nil { 50 | log.Fatal(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sauce.go: -------------------------------------------------------------------------------- 1 | // sauce.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "encoding/binary" 20 | "io" 21 | "os" 22 | ) 23 | 24 | // SauceID is the SAUCE record identifier tagged onto the file data 25 | const SauceID = "SAUCE" 26 | 27 | // SauceInfo contains the bulk of the SAUCE record 28 | type SauceInfo struct { 29 | ID [5]byte 30 | Version [2]byte 31 | Title [35]byte 32 | Author [20]byte 33 | Group [20]byte 34 | Date [8]byte 35 | FileSize int32 36 | DataType byte 37 | FileType byte 38 | Tinfo1 uint16 39 | Tinfo2 uint16 40 | Tinfo3 uint16 41 | Tinfo4 uint16 42 | Comments byte 43 | Flags byte 44 | Filler [22]byte 45 | } 46 | 47 | // Sauce - container structure for sauceInfo and variable length comments 48 | // This feels like a bit of a hack 49 | type Sauce struct { 50 | Sauceinf SauceInfo 51 | CommentLines []string 52 | } 53 | 54 | // Internal constants 55 | const recordSize = 128 56 | const commentSize = 64 57 | const commentID = "COMNT" 58 | 59 | // TODO: Put this check function into a utils file 60 | // Simple error checking to reduce duplicated code 61 | func check(e error) { 62 | if e != nil { 63 | panic(e) 64 | } 65 | } 66 | 67 | // ReadFileName reads SAUCE via a filename. 68 | func readFileName(fileName string) *Sauce { 69 | file, err := os.Open(fileName) 70 | check(err) 71 | 72 | record := readFile(file) 73 | file.Close() 74 | 75 | return record 76 | } 77 | 78 | // ReadFile - Read SAUCE via a FILE pointer. 79 | // TODO: This doesn't trap OOM errors 80 | // TODO: This seems redundant now, can we eliminate this? 81 | func readFile(file *os.File) *Sauce { 82 | record := readRecord(file) 83 | return record 84 | } 85 | 86 | // ReadRecord parses a SAUCE record from a data stream 87 | func readRecord(stream io.ReadSeeker) *Sauce { 88 | _, err := stream.Seek(0-recordSize, 2) 89 | 90 | if err != nil { 91 | return nil 92 | } 93 | 94 | var record Sauce 95 | var sinfo SauceInfo 96 | err = binary.Read(stream, binary.LittleEndian, &sinfo) 97 | check(err) 98 | 99 | if string(sinfo.ID[:]) == SauceID { 100 | var comments []string 101 | if sinfo.Comments > 0 { 102 | comments = readComments(stream, int(sinfo.Comments)) 103 | } 104 | record = Sauce{Sauceinf: sinfo, CommentLines: comments} 105 | } else { 106 | record = Sauce{} 107 | } 108 | 109 | return &record 110 | } 111 | 112 | func readComments(stream io.ReadSeeker, comments int) []string { 113 | var commentLines []string 114 | 115 | _, err := stream.Seek(0-(recordSize+5+commentSize*int64(comments)), 2) 116 | check(err) 117 | 118 | ID := make([]byte, 6) 119 | stream.Read(ID) 120 | idString := string(ID[:6]) 121 | 122 | if idString != commentID { 123 | return nil 124 | } 125 | 126 | // TODO Error checking 127 | for i := 0; i < comments; i++ { 128 | buf := make([]byte, commentSize+1) 129 | 130 | stream.Read(buf) 131 | 132 | commentLines = append(commentLines, string(buf[:])) 133 | } 134 | 135 | return commentLines 136 | } 137 | -------------------------------------------------------------------------------- /tundra.go: -------------------------------------------------------------------------------- 1 | // tundra.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "fmt" 20 | "image" 21 | "image/color" 22 | "image/draw" 23 | "os" 24 | ) 25 | 26 | func tundra(inputFileBuffer []byte, inputFileSize int64, columns int, fontName string, bits int) image.Image { 27 | // some type declarations 28 | var f font 29 | 30 | // font selection 31 | alSelectFont(&f, fontName) 32 | 33 | // libgd image pointers 34 | var imTundra draw.Image 35 | 36 | // extract tundra header 37 | tundraVersion := inputFileBuffer[0] 38 | 39 | // need to add check for "TUNDRA24" string in the header 40 | if tundraVersion != 24 { 41 | fmt.Printf("\nInput file is not a TUNDRA file.\n\n") 42 | os.Exit(4) 43 | } 44 | 45 | // read tundra file a first time to find the image size 46 | var character, loop, positionX, positionY int 47 | 48 | var colorBackground, colorForeground color.RGBA 49 | 50 | loop = 9 51 | 52 | for loop < int(inputFileSize) { 53 | if positionX == 80 { 54 | positionX = 0 55 | positionY++ 56 | } 57 | 58 | character = int(inputFileBuffer[loop]) 59 | 60 | if character == 1 { 61 | positionY = (int(inputFileBuffer[loop+1]) << 24) + (int(inputFileBuffer[loop+2]) << 16) + (int(inputFileBuffer[loop+3]) << 8) + int(inputFileBuffer[loop+4]) 62 | 63 | positionX = (int(inputFileBuffer[loop+5]) << 24) + (int(inputFileBuffer[loop+6]) << 16) + (int(inputFileBuffer[loop+7]) << 8) + int(inputFileBuffer[loop+8]) 64 | 65 | loop += 8 66 | } 67 | 68 | if character == 2 { 69 | character = int(inputFileBuffer[loop+1]) 70 | loop += 5 71 | } 72 | 73 | if character == 4 { 74 | character = int(inputFileBuffer[loop+1]) 75 | loop += 5 76 | } 77 | 78 | if character == 6 { 79 | character = int(inputFileBuffer[loop+1]) 80 | loop += 9 81 | } 82 | 83 | if character != 1 && character != 2 && character != 4 && character != 6 { 84 | positionX++ 85 | } 86 | 87 | loop++ 88 | } 89 | positionY++ 90 | 91 | imTundra = image.NewRGBA(image.Rect(0, 0, columns*bits, positionY*f.sizeY)) 92 | 93 | if imTundra == nil { 94 | fmt.Printf("\nError, can't allocate buffer image memory.\n\n") 95 | os.Exit(6) 96 | } 97 | 98 | black := color.RGBA{0, 0, 0, 255} 99 | draw.Draw(imTundra, imTundra.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 100 | 101 | // process tundra 102 | positionX = 0 103 | positionY = 0 104 | 105 | loop = 9 106 | 107 | for loop < int(inputFileSize) { 108 | if positionX == columns { 109 | positionX = 0 110 | positionY++ 111 | } 112 | 113 | character = int(inputFileBuffer[loop]) 114 | 115 | if character == 1 { 116 | positionY = (int(inputFileBuffer[loop+1]) << 24) + (int(inputFileBuffer[loop+2]) << 16) + (int(inputFileBuffer[loop+3]) << 8) + int(inputFileBuffer[loop+4]) 117 | 118 | positionX = (int(inputFileBuffer[loop+5]) << 24) + (int(inputFileBuffer[loop+6]) << 16) + (int(inputFileBuffer[loop+7]) << 8) + int(inputFileBuffer[loop+8]) 119 | 120 | loop += 8 121 | } 122 | 123 | if character == 2 { 124 | colorForeground = color.RGBA{inputFileBuffer[loop+3], inputFileBuffer[loop+4], inputFileBuffer[loop+5], 255} 125 | 126 | character = int(inputFileBuffer[loop+1]) 127 | 128 | loop += 5 129 | } 130 | 131 | if character == 4 { 132 | colorBackground = color.RGBA{inputFileBuffer[loop+3], inputFileBuffer[loop+4], inputFileBuffer[loop+5], 255} 133 | 134 | character = int(inputFileBuffer[loop+1]) 135 | 136 | loop += 5 137 | } 138 | 139 | if character == 6 { 140 | colorForeground = color.RGBA{inputFileBuffer[loop+3], inputFileBuffer[loop+4], inputFileBuffer[loop+5], 255} 141 | colorBackground = color.RGBA{inputFileBuffer[loop+7], inputFileBuffer[loop+8], inputFileBuffer[loop+9], 255} 142 | 143 | character = int(inputFileBuffer[loop+1]) 144 | 145 | loop += 9 146 | } 147 | 148 | if character != 1 && character != 2 && character != 4 && character != 6 { 149 | alDrawChar(imTundra, f.data, bits, f.sizeY, positionX, positionY, colorBackground, colorForeground, byte(character)) 150 | 151 | positionX++ 152 | } 153 | 154 | loop++ 155 | } 156 | 157 | return imTundra 158 | } 159 | -------------------------------------------------------------------------------- /xbin.go: -------------------------------------------------------------------------------- 1 | // xbin.go 2 | // go-ansi 3 | // 4 | // Copyright (C) 2017 ActiveState Software Inc. 5 | // Written by Pete Garcin (@rawktron) 6 | // 7 | // Based on ansilove/C 8 | // Copyright (C) 2011-2017 Stefan Vogt, Brian Cassidy, and Frederic Cambus. 9 | // All rights reserved. 10 | // ansilove/C is licensed under the BSD-2 License. 11 | // 12 | // go-ansi is licensed under the BSD 3-Clause License. 13 | // See the file LICENSE for details. 14 | // 15 | 16 | package goansi 17 | 18 | import ( 19 | "fmt" 20 | "image" 21 | "image/color" 22 | "image/draw" 23 | "os" 24 | ) 25 | 26 | // Xbin processes inputFileBuffer and outputs image data 27 | func xbin(inputFileBuffer []byte, inputFileSize int64) image.Image { 28 | var f font 29 | 30 | if string(inputFileBuffer[0:4]) == "XBIN\x1a" { 31 | fmt.Print("\nNot an XBin.\n\n") 32 | os.Exit(4) 33 | } 34 | 35 | var xbinWidth, xbinHeight, xbinFontSize, xbinFlags int 36 | 37 | xbinWidth = (int(inputFileBuffer[6]) << 8) + int(inputFileBuffer[5]) 38 | xbinHeight = (int(inputFileBuffer[8]) << 8) + int(inputFileBuffer[7]) 39 | xbinFontSize = int(inputFileBuffer[9]) 40 | xbinFlags = int(inputFileBuffer[10]) 41 | 42 | var imXBIN draw.Image 43 | 44 | imXBIN = image.NewRGBA(image.Rect(0, 0, 8*int(xbinWidth), int(xbinFontSize)*int(xbinHeight))) 45 | 46 | if imXBIN == nil { 47 | fmt.Printf("\nError, can't allocate buffer image memory.\n\n") 48 | os.Exit(6) 49 | } 50 | 51 | black := color.RGBA{0, 0, 0, 255} 52 | draw.Draw(imXBIN, imXBIN.Bounds(), &image.Uniform{black}, image.ZP, draw.Src) 53 | 54 | var colors [16]color.RGBA 55 | offset := 11 56 | 57 | // palette 58 | if (xbinFlags & 1) == 1 { 59 | var index int 60 | 61 | for loop := 0; loop < 16; loop++ { 62 | index = (loop * 3) + offset 63 | 64 | colors[loop] = color.RGBA{(inputFileBuffer[index]<<2 | inputFileBuffer[index]>>4), 65 | (inputFileBuffer[index+1]<<2 | inputFileBuffer[index+1]>>4), 66 | (inputFileBuffer[index+2]<<2 | inputFileBuffer[index+2]>>4), 255} 67 | } 68 | 69 | offset += 48 70 | } else { 71 | colors[0] = color.RGBA{0, 0, 0, 255} 72 | colors[1] = color.RGBA{0, 0, 170, 255} 73 | colors[2] = color.RGBA{0, 170, 0, 255} 74 | colors[3] = color.RGBA{0, 170, 170, 255} 75 | colors[4] = color.RGBA{170, 0, 0, 255} 76 | colors[5] = color.RGBA{170, 0, 170, 255} 77 | colors[6] = color.RGBA{170, 85, 0, 255} 78 | colors[7] = color.RGBA{170, 170, 170, 255} 79 | colors[8] = color.RGBA{85, 85, 85, 255} 80 | colors[9] = color.RGBA{85, 85, 255, 255} 81 | colors[10] = color.RGBA{85, 255, 85, 255} 82 | colors[11] = color.RGBA{85, 255, 255, 255} 83 | colors[12] = color.RGBA{255, 85, 85, 255} 84 | colors[13] = color.RGBA{255, 85, 255, 255} 85 | colors[14] = color.RGBA{255, 255, 85, 255} 86 | colors[15] = color.RGBA{255, 255, 255, 255} 87 | } 88 | 89 | // font 90 | if (xbinFlags & 2) == 2 { 91 | var numchars int 92 | 93 | if (xbinFlags & 0x10) != 0 { 94 | numchars = 512 95 | } else { 96 | numchars = 256 97 | } 98 | 99 | f.data = inputFileBuffer[offset : offset+(int(xbinFontSize)*numchars)] 100 | f.sizeY = int(xbinFontSize) 101 | f.sizeX = 8 102 | f.isAmigaFont = false 103 | 104 | offset += (int(xbinFontSize) * numchars) 105 | } else { 106 | // using default 80x25 font 107 | alSelectFont(&f, "80x25") 108 | } 109 | 110 | var positionX, positionY int = 0, 0 111 | var character, attribute, colorForeground, colorBackground int 112 | 113 | // read compressed xbin 114 | if (xbinFlags & 4) == 4 { 115 | for offset < int(inputFileSize) && positionY != int(xbinHeight) { 116 | ctype := inputFileBuffer[offset] & 0xC0 117 | counter := (inputFileBuffer[offset] & 0x3F) + 1 118 | 119 | character = -1 120 | attribute = -1 121 | 122 | offset++ 123 | for i := counter; i > 0; i-- { 124 | // none 125 | if ctype == 0 { 126 | character = int(inputFileBuffer[offset]) 127 | attribute = int(inputFileBuffer[offset+1]) 128 | offset += 2 129 | } else if ctype == 0x40 { 130 | // char 131 | if character == -1 { 132 | character = int(inputFileBuffer[offset]) 133 | offset++ 134 | } 135 | attribute = int(inputFileBuffer[offset]) 136 | offset++ 137 | } else if ctype == 0x80 { 138 | // attr 139 | if attribute == -1 { 140 | attribute = int(inputFileBuffer[offset]) 141 | offset++ 142 | } 143 | character = int(inputFileBuffer[offset]) 144 | offset++ 145 | } else { 146 | // both 147 | if character == -1 { 148 | character = int(inputFileBuffer[offset]) 149 | offset++ 150 | } 151 | if attribute == -1 { 152 | attribute = int(inputFileBuffer[offset]) 153 | offset++ 154 | } 155 | } 156 | 157 | colorBackground = (attribute & 240) >> 4 158 | colorForeground = attribute & 15 159 | 160 | alDrawChar(imXBIN, f.data, 8, 16, positionX, positionY, colors[colorBackground], colors[colorForeground], byte(character)) 161 | 162 | positionX++ 163 | 164 | if positionX == int(xbinWidth) { 165 | positionX = 0 166 | positionY++ 167 | } 168 | } 169 | } 170 | } else { 171 | // read uncompressed xbin 172 | for offset < int(inputFileSize) && positionY != int(xbinHeight) { 173 | if positionX == int(xbinWidth) { 174 | positionX = 0 175 | positionY++ 176 | } 177 | 178 | character = int(inputFileBuffer[offset]) 179 | attribute = int(inputFileBuffer[offset+1]) 180 | 181 | colorBackground = (attribute & 240) >> 4 182 | colorForeground = attribute & 15 183 | 184 | alDrawChar(imXBIN, f.data, 8, int(xbinFontSize), positionX, positionY, colors[colorBackground], colors[colorForeground], byte(character)) 185 | 186 | positionX++ 187 | offset += 2 188 | } 189 | } 190 | 191 | return imXBIN 192 | } 193 | --------------------------------------------------------------------------------