Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jed Williamson committed Dec 9, 2022
0 parents commit 7373ecb
Show file tree
Hide file tree
Showing 6 changed files with 411 additions and 0 deletions.
Binary file added asciicam
Binary file not shown.
154 changes: 154 additions & 0 deletions asciify/asciify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package asciify

import (
"fmt"
"image"
"math"
"time"

"github.com/blackjack/webcam"
"github.com/gdamore/tcell/v2"
"gocv.io/x/gocv"
)

var colour tcell.Color
var ascii_symbols = []rune(string(".,-~:;=!*#$@"))

type Settings struct {
Brightness_caps map[string]float64
Contrast_caps map[string]float64
Brightness float64
Contrast float64
Supported_resolutions []string
}

type Camera struct {
Cap *gocv.VideoCapture
Cap_width float64
Cap_height float64
}

func Newcam(device string) (*Camera, *Settings, error) {
s := Settings{}

cam_caps, err := webcam.Open(device)
if err != nil {
return nil, nil, err
}
defer cam_caps.Close()

capmap := cam_caps.GetControls()

s.Brightness_caps = make(map[string]float64)
s.Brightness_caps["min"] = float64(capmap[webcam.ControlID(0x00980900)].Min)
s.Brightness_caps["max"] = float64(capmap[webcam.ControlID(0x00980900)].Max)
brightness, err := cam_caps.GetControl(webcam.ControlID(0x00980900))
if err != nil {
return nil, nil, err
}
s.Brightness = float64(brightness)

s.Contrast_caps = make(map[string]float64)
s.Contrast_caps["min"] = float64(capmap[webcam.ControlID(0x00980901)].Min)
s.Contrast_caps["max"] = float64(capmap[webcam.ControlID(0x00980901)].Max)
contrast, err := cam_caps.GetControl(webcam.ControlID(0x00980901))
if err != nil {
return nil, nil, err
}
s.Contrast = float64(contrast)

s.Supported_resolutions = []string{}
resolutions := cam_caps.GetSupportedFrameSizes(webcam.PixelFormat(1196444237))
for _, fs := range resolutions {
s.Supported_resolutions = append(s.Supported_resolutions, fs.GetString())
}

cam := Camera{}

cam.Cap, err = gocv.OpenVideoCaptureWithAPI(device, 200)
if err != nil {
return nil, nil, err
}
cam.Cap_width = cam.Cap.Get(gocv.VideoCaptureFrameWidth)
cam.Cap_height = cam.Cap.Get(gocv.VideoCaptureFrameHeight)

return &cam, &s, err
}

func CamToAscii(cam *Camera, frame *gocv.Mat, canvas tcell.Screen, settings *Settings) {
colour = tcell.NewRGBColor(18, 181, 131)
style := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(colour)
canvas.Clear()
cam.Cap.Set(gocv.VideoCaptureBrightness, settings.Brightness)
cam.Cap.Set(gocv.VideoCaptureContrast, settings.Contrast)
prev_frame_time := time.Now()
greyFrame := gocv.NewMat()
_ = greyFrame
gocv.CvtColor(*frame, &greyFrame, gocv.ColorBGRToGray)
downFrame := gocv.NewMat()
term_width, term_height := canvas.Size()
scale := math.Min(cam.Cap_width/float64(term_width), cam.Cap_height/float64(term_height))
size := image.Point{X: int(cam.Cap_width / scale), Y: int(cam.Cap_height / (scale * 2.5))}
gocv.Resize(greyFrame, &downFrame, size, 0, 0, gocv.InterpolationArea)
for y := 0; y < downFrame.Rows(); y++ {
for x := 0; x < downFrame.Cols(); x++ {
sym := ascii_symbols[int(downFrame.GetUCharAt(y, x)/22)]
canvas.SetContent(x, y, sym, nil, style)
}
}
new_frame_time := time.Now()
fps := int(1 / (time.Since(prev_frame_time).Seconds()))
prev_frame_time = new_frame_time
for i, r := range fmt.Sprintf("%vFPS, Brightness=%v, Contrast=%v", fps, settings.Brightness, settings.Contrast) {
canvas.SetContent(i, 0, r, nil, style)
}
canvas.Show()
}

// func AsciiFy(cam *Camera, settings *Settings) {
// err := termbox.Init()
// if err != nil {
// panic(err)
// }
// defer termbox.Close()
// termbox.SetOutputMode(termbox.OutputRGB)
// event_queue := make(chan termbox.Event, 1)
// go func() {
// for {
// event_queue <- termbox.PollEvent()
// }
// }()
// mainloop:
// for {
// select {
// case control := <-event_queue:
// if control.Type == termbox.EventKey && control.Key == termbox.KeyEsc || control.Key == termbox.KeyCtrlC {
// break mainloop
// }
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowUp {
// if settings.Brightness < settings.Brightness_caps["max"] {
// settings.Brightness += 1
// }
// }
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowDown {
// if settings.Brightness > settings.Brightness_caps["min"] {
// settings.Brightness -= 1
// }
// }
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowRight {
// if settings.Contrast < settings.Contrast_caps["max"] {
// settings.Contrast += 1
// }
// }
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowLeft {
// if settings.Contrast > settings.Contrast_caps["min"] {
// settings.Contrast -= 1
// }
// }
// default:
// frame := gocv.NewMat()
// cam.Cap.Read(&frame)
// camtoascii(cam, &frame, settings)
// }
// }
// }
185 changes: 185 additions & 0 deletions cmd/asciicam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package cmd

import (
"fmt"
"strconv"
"strings"

"github.com/lupinelab/asciicam/asciify"

"github.com/gdamore/tcell/v2"
"github.com/spf13/cobra"
"gocv.io/x/gocv"
)

var asciicamCmd = &cobra.Command{
Use: "asciicam [device]",
Short: "Turn your camera into ASCII",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// TODO add regex to match expected video device path
// Get camera and capabilities
cam, settings, err := asciify.Newcam(args[0])
if err != nil {
panic(err.Error())
}
defer cam.Cap.Close()

canvas, err := tcell.NewScreen()
if err != nil {
panic(err)
}
err = canvas.Init()
if err != nil {
panic(err)
}
defer canvas.Fini()
defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorReset)
canvas.SetStyle(defStyle)
// err = termbox.Init()
// if err != nil {
// panic(err)
// }
// defer termbox.Close()
// termbox.SetOutputMode(termbox.OutputRGB)
res_sel_screen := []string{}
res_sel_screen = append(res_sel_screen, fmt.Sprintf("Press a number key to choose a resolution:"))
for i, fs := range settings.Supported_resolutions {
res_sel_screen = append(res_sel_screen, fmt.Sprintf("%v) %v", i, fs))
}
for i, l := range res_sel_screen {
for n, r := range l {
canvas.SetContent(n, i, r, nil, defStyle)
}
}
canvas.Show()
var resolution string
inputloop:
for {
input := canvas.PollEvent()
switch input := input.(type) {
case *tcell.EventKey:
for i, fs := range settings.Supported_resolutions {
value, err := strconv.Atoi(string(input.Rune()))
if err != nil {
panic(err)
}
if value == i {
resolution = fs
break inputloop
}
// input := termbox.PollEvent()
// switch input.Type {
// case termbox.EventKey:
// for i, fs := range settings.Supported_resolutions {
// if int(input.Key) == i {
// resolution = fs
// break inputloop
// }
// }
// }
}
}
}
fWH := strings.Split(resolution, "x")
fW, err := strconv.ParseFloat(fWH[0], 32)
if err != nil {
panic(err)
}
fH, err := strconv.ParseFloat(fWH[1], 32)
if err != nil {
panic(err)
}
cam.Cap.Set(gocv.VideoCaptureFrameWidth, fW)
cam.Cap.Set(gocv.VideoCaptureFrameHeight, fH)
// event_queue := make(chan tcell.Event, 1)
// go func() {
// for {
// event_queue <- canvas.PollEvent()
// }
// }()
quit := make(chan struct{})
go func () {
for {
control := canvas.PollEvent()
// select {
// case control := <-event_queue:
switch control := control.(type) {
case *tcell.EventKey:
if control.Key() == tcell.KeyEsc || control.Key() == tcell.KeyCtrlC {
close(quit)
return
}
// if control.Type == tcell.EventKey && control.Key == termbox.KeyEsc || control.Key == termbox.KeyCtrlC {
// break mainloop
// }
if control.Key() == tcell.KeyUp {
if settings.Brightness < settings.Brightness_caps["max"] {
settings.Brightness += 1
}
}
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowUp {
// if settings.Brightness < settings.Brightness_caps["max"] {
// settings.Brightness += 1
// }
// }
if control.Key() == tcell.KeyDown {
if settings.Brightness > settings.Brightness_caps["min"] {
settings.Brightness -= 1
}
}
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowDown {
// if settings.Brightness > settings.Brightness_caps["min"] {
// settings.Brightness -= 1
// }
// }
if control.Key() == tcell.KeyRight {
if settings.Contrast < settings.Contrast_caps["max"] {
settings.Contrast += 1
}
}
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowRight {
// if settings.Contrast < settings.Contrast_caps["max"] {
// settings.Contrast += 1
// }
// }
if control.Key() == tcell.KeyLeft {
if settings.Contrast > settings.Contrast_caps["min"] {
settings.Contrast -= 1
}
}
// if control.Type == termbox.EventKey && control.Key == termbox.KeyArrowLeft {
// if settings.Contrast > settings.Contrast_caps["min"] {
// settings.Contrast -= 1
// }
// }
// default:
}
}
}()
mainloop:
for {
select {
case <-quit:
break mainloop
default:
frame := gocv.NewMat()
cam.Cap.Read(&frame)
asciify.CamToAscii(cam, &frame, canvas, settings)
}
}
},
}

func Execute() error {
return asciicamCmd.Execute()
}

func init() {
asciicamCmd.PersistentFlags().BoolP("list-res", "l", false, "List available resolutions of a device")
asciicamCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
asciicamCmd.MarkFlagRequired("")
asciicamCmd.PersistentFlags().Lookup("help").Hidden = true
cobra.EnableCommandSorting = false
asciicamCmd.CompletionOptions.DisableDefaultCmd = true
}
22 changes: 22 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/lupinelab/asciicam

go 1.19

require (
github.com/blackjack/webcam v0.0.0-20220329180758-ba064708e165
github.com/gdamore/tcell/v2 v2.5.3
github.com/spf13/cobra v1.6.1
gocv.io/x/gocv v0.31.0
)

require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect
)
Loading

0 comments on commit 7373ecb

Please sign in to comment.