-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jed Williamson
committed
Dec 9, 2022
0 parents
commit 7373ecb
Showing
6 changed files
with
411 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
// } | ||
// } | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
Oops, something went wrong.