Skip to content

Instantly share code, notes, and snippets.

@robopuff
Last active December 12, 2018 10:06
Show Gist options
  • Save robopuff/a7e403661fcc78c17cc11dcc60cb3cec to your computer and use it in GitHub Desktop.
Save robopuff/a7e403661fcc78c17cc11dcc60cb3cec to your computer and use it in GitHub Desktop.
Paint image in terminal
package main
import (
"flag"
"fmt"
"image"
"image/color"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"math"
"os"
"strings"
"syscall"
"unsafe"
"github.com/nfnt/resize"
)
const pixel = "\xE2\x96\x84"
const rc = "\033[0m"
type Color struct {
Red, Green, Blue uint32
}
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
func main() {
width := flag.Int("w", 0, "Change image width")
test := flag.Bool("t", false, "Check test chars")
palette := flag.Bool("p", false, "Draw palette")
flag.Parse()
if *width == 0 {
*width = getTerminalWidth()
}
if *test {
drawTest()
return
}
if *palette {
drawPalette(uint(*width))
return
}
if len(os.Args) < 2 {
fmt.Println("Usage: paint [PARAMS] <FILE>")
return
}
filepath := os.Args[len(os.Args)-1]
if filepath == "" {
fmt.Println("No file provided")
return
}
file, err := os.Open(filepath)
if err != nil {
panic(err)
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
panic(err)
}
drawImage(img, uint(*width))
}
func drawTest() {
color1 := Color{255, 80, 255}
color2 := Color{105, 80, 255}
drawChar(color1, color2, "", false)
fmt.Print(strings.Repeat(pixel, 3) + rc)
drawChar(color2, color1, "", false)
fmt.Print(strings.Repeat(pixel, 3) + rc + " -" + pixel)
drawChar(color1, color2, pixel, false)
drawChar(color2, color1, pixel, true)
fmt.Println(pixel + "-")
var r uint32 = 0
for r = 0; r < 256; r += 5 {
drawChar(Color{r, 0, 0}, Color{0, r, 0}, pixel, false)
}
fmt.Println()
for r = 0; r < 256; r += 5 {
drawChar(Color{0, 0, r}, Color{r, r, r}, pixel, false)
}
fmt.Println()
}
func drawPalette(imgWidth uint) {
img := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{255, 255}})
for y := 0; y < 255; y++ {
for x := 0; x < 255*2; x++ {
newColor := Color{
uint32(255 - x),
uint32(255 - y),
uint32((x + y) / 2),
}
img.Set(x, y, color.RGBA{uint8(newColor.Red), uint8(newColor.Green), uint8(newColor.Blue), 255})
}
}
drawImage(img, imgWidth)
}
func drawImage(img image.Image, imgWidth uint) {
if imgWidth == 0 {
imgWidth = uint(img.Bounds().Max.Y)
}
img = resize.Resize(imgWidth, getImageHeight(imgWidth, img), img, resize.Lanczos3)
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
for y := 0; y < height; y++ {
if y%2 == 0 {
continue
}
for x := 0; x < width; x++ {
upperColor := rgbaToColor(img.At(x, y).RGBA())
lowerColor := upperColor
if y < height {
lowerColor = rgbaToColor(img.At(x, y+1).RGBA())
}
drawChar(upperColor, lowerColor, pixel, true)
}
fmt.Println(rc)
}
}
func drawChar(upper Color, lower Color, char string, clean bool) {
if upper == lower {
fmt.Print(getBackground(upper) + " ")
} else {
fmt.Print(getBackground(upper) + getForeground(lower, char))
}
if clean {
fmt.Print(rc)
}
}
func getBackground(color Color) string {
return fmt.Sprintf("\033[48;2;%d;%d;%dm", color.Red, color.Green, color.Blue)
}
func getForeground(color Color, char string) string {
return fmt.Sprintf("\033[38;2;%d;%d;%dm%s", color.Red, color.Green, color.Blue, char)
}
func rgbaToColor(r, g, b, a uint32) Color {
return Color{r / 257, g / 257, b / 257}
}
func getImageHeight(width uint, img image.Image) uint {
ratio := float64(img.Bounds().Max.X) / float64(img.Bounds().Max.Y)
return uint(math.Round(float64(width) / ratio))
}
func getTerminalWidth() int {
ws := &winsize{}
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(syscall.Stdin),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)))
if int(retCode) == -1 {
panic(errno)
}
return int(ws.Col)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment