ansigo

Simple ANSi to PNG converter written in pure Go
Log | Files | Refs | README | LICENSE

ansigo.go (6389B)


      1 // AnsiGo 1.01
      2 // Copyright (c) 2012-2019, Frederic Cambus
      3 // https://github.com/fcambus/ansigo
      4 //
      5 // Created:      2012-02-14
      6 // Last Updated: 2019-06-11
      7 //
      8 // AnsiGo is released under the BSD 2-Clause license.
      9 // See LICENSE file for details.
     10 package main
     11 
     12 import (
     13 	"fmt"
     14 	"image"
     15 	"image/draw"
     16 	"image/png"
     17 	"io/ioutil"
     18 	"os"
     19 	"strconv"
     20 	"strings"
     21 )
     22 
     23 func main() {
     24 	fmt.Println("-------------------------------------------------------------------------------\n                  AnsiGo 1.01 (c) by Frederic CAMBUS 2012-2019\n-------------------------------------------------------------------------------\n")
     25 
     26 	// Check input parameters and show usage
     27 	if len(os.Args) != 2 {
     28 		fmt.Println("USAGE:    ansigo inputfile\n")
     29 		fmt.Println("EXAMPLES: ansigo ansi.ans\n")
     30 		os.Exit(1)
     31 	}
     32 
     33 	input := os.Args[1]
     34 	output := input + ".png"
     35 
     36 	fmt.Println("Input File:", input)
     37 	fmt.Println("Output File:", output)
     38 
     39 	var ansi Ansi
     40 	ansi.SetPalette()
     41 	ansi.SetFont()
     42 
     43 	// Load input file
     44 	data, err := ioutil.ReadFile(input)
     45 	if err != nil {
     46 		fmt.Println("\nERROR: Can't open or read", input, "\n")
     47 		os.Exit(1)
     48 	}
     49 
     50 	// Process ANSI
     51 	for i := 0; i < len(data); i++ {
     52 		ansi.character = data[i]
     53 
     54 		// 80th column wrapping
     55 		if ansi.positionX == 80 {
     56 			ansi.positionY++
     57 			ansi.positionX = 0
     58 		}
     59 
     60 		// CR (Carriage Return)
     61 		if ansi.character == '\r' {
     62 			continue
     63 		}
     64 
     65 		// LF (Line Feed)
     66 		if ansi.character == '\n' {
     67 			ansi.positionY++
     68 			ansi.positionX = 0
     69 			continue
     70 		}
     71 
     72 		// HT (Horizontal Tabulation)
     73 		if ansi.character == '\t' {
     74 			ansi.positionX += 8
     75 			continue
     76 		}
     77 
     78 		// SUB (Substitute)
     79 		if ansi.character == '\x1a' {
     80 			break
     81 		}
     82 
     83 		// ANSI Sequence: ESC (Escape) + [
     84 		if ansi.character == '\x1b' && data[i+1] == '[' {
     85 			ansiSequence := []byte{}
     86 
     87 		sequence:
     88 			for j := 0; j < 12; j++ {
     89 				ansiSequenceCharacter := data[i+2+j]
     90 
     91 				switch ansiSequenceCharacter {
     92 				case 'H', 'f': // Cursor Position
     93 					ansiSequenceValues := strings.SplitN(string(ansiSequence), ";", -1)
     94 
     95 					valueY, _ := strconv.Atoi(ansiSequenceValues[0])
     96 					ansi.positionY = valueY - 1
     97 
     98 					valueX, _ := strconv.Atoi(ansiSequenceValues[1])
     99 					ansi.positionX = valueX - 1
    100 
    101 					i += j + 2
    102 					break sequence
    103 
    104 				case 'A': // Cursor Up
    105 					valueY, _ := strconv.Atoi(string(ansiSequence))
    106 					if valueY == 0 {
    107 						valueY++
    108 					}
    109 
    110 					ansi.positionY = ansi.positionY - valueY
    111 
    112 					i += j + 2
    113 					break sequence
    114 
    115 				case 'B': // Cursor Down
    116 					valueY, _ := strconv.Atoi(string(ansiSequence))
    117 					if valueY == 0 {
    118 						valueY++
    119 					}
    120 
    121 					ansi.positionY = ansi.positionY + valueY
    122 
    123 					i += j + 2
    124 					break sequence
    125 
    126 				case 'C': // Cursor Forward
    127 					valueX, _ := strconv.Atoi(string(ansiSequence))
    128 					if valueX == 0 {
    129 						valueX++
    130 					}
    131 
    132 					ansi.positionX = ansi.positionX + valueX
    133 					if ansi.positionX > 80 {
    134 						ansi.positionX = 80
    135 					}
    136 
    137 					i += j + 2
    138 					break sequence
    139 
    140 				case 'D': // Cursor Backward
    141 					valueX, _ := strconv.Atoi(string(ansiSequence))
    142 					if valueX == 0 {
    143 						valueX++
    144 					}
    145 
    146 					ansi.positionX = ansi.positionX - valueX
    147 					if ansi.positionX < 0 {
    148 						ansi.positionX = 0
    149 					}
    150 
    151 					i += j + 2
    152 					break sequence
    153 
    154 				case 's': // Save Cursor Position
    155 					ansi.savedPositionY = ansi.positionY
    156 					ansi.savedPositionX = ansi.positionX
    157 
    158 					i += j + 2
    159 					break sequence
    160 
    161 				case 'u': // Restore Cursor Position
    162 					ansi.positionY = ansi.savedPositionY
    163 					ansi.positionX = ansi.savedPositionX
    164 
    165 					i += j + 2
    166 					break sequence
    167 
    168 				case 'J': // Erase Display
    169 					value, _ := strconv.Atoi(string(ansiSequence))
    170 
    171 					if value == 2 {
    172 						ansi.buffer = nil
    173 
    174 						ansi.positionX = 0
    175 						ansi.positionY = 0
    176 						ansi.sizeX = 0
    177 						ansi.sizeY = 0
    178 					}
    179 
    180 					i += j + 2
    181 					break sequence
    182 
    183 				case 'm': // Set Graphic Rendition
    184 					ansiSequenceValues := strings.SplitN(string(ansiSequence), ";", -1)
    185 
    186 					for j := 0; j < len(ansiSequenceValues); j++ {
    187 						valueColor, _ := strconv.Atoi(ansiSequenceValues[j])
    188 
    189 						switch valueColor {
    190 						case 0:
    191 							ansi.colorBackground = 0
    192 							ansi.colorForeground = 7
    193 							ansi.bold = false
    194 
    195 						case 1:
    196 							ansi.colorForeground += 8
    197 							ansi.bold = true
    198 
    199 						case 5:
    200 							ansi.colorBackground += 8
    201 
    202 						case 30, 31, 32, 33, 34, 35, 36, 37:
    203 							ansi.colorForeground = valueColor - 30
    204 							if ansi.bold {
    205 								ansi.colorForeground += 8
    206 							}
    207 
    208 						case 40, 41, 42, 43, 44, 45, 46, 47:
    209 							ansi.colorBackground = valueColor - 40
    210 						}
    211 					}
    212 
    213 					i += j + 2
    214 					break sequence
    215 
    216 				case 'h', 'l': // Skipping Set Mode And Reset Mode Sequences
    217 					i += j + 2
    218 					break sequence
    219 				}
    220 
    221 				ansiSequence = append(ansiSequence, ansiSequenceCharacter)
    222 			}
    223 		} else {
    224 			// Record Number Of Columns And Lines Used
    225 			if ansi.positionX > ansi.sizeX {
    226 				ansi.sizeX = ansi.positionX
    227 			}
    228 			if ansi.positionY > ansi.sizeY {
    229 				ansi.sizeY = ansi.positionY
    230 			}
    231 
    232 			// Write Current Character Info In A Temporary Array
    233 			ansi.buffer = append(ansi.buffer, Character{ansi.colorBackground, ansi.colorForeground, ansi.positionX, ansi.positionY, ansi.character})
    234 
    235 			ansi.positionX++
    236 		}
    237 	}
    238 
    239 	// Allocate Image Buffer Memory
    240 	canvasSize := image.Rect(0, 0, 640, (ansi.sizeY+1)*16)
    241 	canvas := image.NewRGBA(canvasSize)
    242 
    243 	// Draw The Canvas Background
    244 	draw.Draw(canvas, canvas.Bounds(), &image.Uniform{ansi.palette[0]}, image.ZP, draw.Src)
    245 
    246 	// Render ANSI
    247 	for i := 0; i < len(ansi.buffer); i++ {
    248 		character := ansi.buffer[i]
    249 
    250 		// Set Background
    251 		draw.Draw(canvas, image.Rect(character.positionX*8, character.positionY*16, character.positionX*8+8, character.positionY*16+16), &image.Uniform{ansi.palette[character.colorBackground]}, image.ZP, draw.Src)
    252 
    253 		// Draw Character
    254 		for line := 0; line < 16; line++ {
    255 			for column := 0; column < 8; column++ {
    256 				if (ansi.font[line+(int(character.code)*16)] & (0x80 >> uint(column))) != 0 {
    257 					canvas.Set(character.positionX*8+column, line+character.positionY*16, ansi.palette[character.colorForeground])
    258 				}
    259 			}
    260 		}
    261 	}
    262 
    263 	// Create Output File
    264 	outputFile, err := os.Create(output)
    265 	if err != nil {
    266 		fmt.Println("ERROR: Can't create output file.")
    267 		os.Exit(1)
    268 	}
    269 
    270 	// Encode PNG image
    271 	if err = png.Encode(outputFile, canvas); err != nil {
    272 		fmt.Println("ERROR: Can't encode PNG file.")
    273 		os.Exit(1)
    274 	}
    275 
    276 	outputFile.Close()
    277 
    278 	fmt.Println("\nSuccessfully created file", output, "\n")
    279 }