From 67b7cd4579c869a84ebc8d711a83a8ef4a65699f Mon Sep 17 00:00:00 2001 From: bel Date: Tue, 4 Apr 2023 22:51:14 -0600 Subject: [PATCH] gr --- go.mod | 21 ++++ go.sum | 43 ++++++++ image.go | 16 +++ image_test.go | 13 +++ testdata/draw.d/go.mod | 7 ++ testdata/draw.d/go.sum | 4 + testdata/draw.d/main.go | 146 ++++++++++++++++++++++++++ testdata/tiv.sh | 2 + testdata/tofugu.d/a-hiragana-0.sm.png | Bin 0 -> 2031 bytes 9 files changed, 252 insertions(+) create mode 100644 image.go create mode 100644 image_test.go create mode 100644 testdata/draw.d/go.mod create mode 100644 testdata/draw.d/go.sum create mode 100644 testdata/draw.d/main.go create mode 100644 testdata/tofugu.d/a-hiragana-0.sm.png diff --git a/go.mod b/go.mod index 735bf00..1d61ce5 100644 --- a/go.mod +++ b/go.mod @@ -6,3 +6,24 @@ require ( github.com/google/uuid v1.3.0 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/charmbracelet/bubbletea v0.12.0 // indirect + github.com/containerd/console v1.0.1 // indirect + github.com/disintegration/gift v1.1.2 // indirect + github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec // indirect + github.com/lucasb-eyer/go-colorful v1.0.3 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/muesli/termenv v0.7.4 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect + github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect + github.com/trashhalo/imgcat v1.2.0 // indirect + golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee // indirect + golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect + golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 // indirect + golang.org/x/text v0.3.0 // indirect +) diff --git a/go.sum b/go.sum index 00b3916..dc3f135 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,48 @@ +github.com/charmbracelet/bubbletea v0.12.0 h1:/pUHp1GWRDyK1TJAWkXrnRH1u8Xc5076oH/J0NHxH+M= +github.com/charmbracelet/bubbletea v0.12.0/go.mod h1:3gZkYELUOiEUOp0bTInkxguucy/xRbGSOcbMs1geLxg= +github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/disintegration/gift v1.1.2 h1:9ZyHJr+kPamiH10FX3Pynt1AxFUob812bU9Wt4GMzhs= +github.com/disintegration/gift v1.1.2/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec h1:YrB6aVr9touOt75I9O1SiancmR2GMg45U9UYf0gtgWg= +github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec/go.mod h1:K0KBFIr1gWu/C1Gp10nFAcAE4hsB7JxE6OgLijrJ8Sk= +github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8= +github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8= +github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/trashhalo/imgcat v1.2.0 h1:vUwdvOqcAxjrqBMH63KQbOKXWcrwy7xFt5l3nfE8K6Q= +github.com/trashhalo/imgcat v1.2.0/go.mod h1:oa4z1WA6THdqcwPKp3VFMe/7CZzzrOfDY3mRTePxtOQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= +golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/image.go b/image.go new file mode 100644 index 0000000..0852052 --- /dev/null +++ b/image.go @@ -0,0 +1,16 @@ +package main + +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/trashhalo/imgcat" +) + +func View(image string) { + model := imgcat.NewModel([]string{image}) + p := tea.NewProgram(model) + p.EnterAltScreen() + defer p.ExitAltScreen() + if err := p.Start(); err != nil { + panic(err) + } +} diff --git a/image_test.go b/image_test.go new file mode 100644 index 0000000..f2c4d6e --- /dev/null +++ b/image_test.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" + "testing" +) + +func TestView(t *testing.T) { + if os.Getenv("INTEGRATION") == "" { + t.SkipNow() + } + View("./testdata/tofugu.d/a-hiragana-0.png") +} diff --git a/testdata/draw.d/go.mod b/testdata/draw.d/go.mod new file mode 100644 index 0000000..4436d37 --- /dev/null +++ b/testdata/draw.d/go.mod @@ -0,0 +1,7 @@ +module gogs.inhome.blapointe.com/anki.d/testdata/draw.d + +go 1.19 + +require github.com/nsf/termbox-go v1.1.1 + +require github.com/mattn/go-runewidth v0.0.9 // indirect diff --git a/testdata/draw.d/go.sum b/testdata/draw.d/go.sum new file mode 100644 index 0000000..509f0d3 --- /dev/null +++ b/testdata/draw.d/go.sum @@ -0,0 +1,4 @@ +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= +github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= diff --git a/testdata/draw.d/main.go b/testdata/draw.d/main.go new file mode 100644 index 0000000..ac8150d --- /dev/null +++ b/testdata/draw.d/main.go @@ -0,0 +1,146 @@ +package main + +import ( + "image" + "image/color" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "log" + "math" + "os" + + termbox "github.com/nsf/termbox-go" +) + +var palette color.Palette = []color.Color{ + attrColor(termbox.ColorBlack), + attrColor(termbox.ColorRed), + attrColor(termbox.ColorGreen), + attrColor(termbox.ColorYellow), + attrColor(termbox.ColorBlue), + attrColor(termbox.ColorMagenta), + attrColor(termbox.ColorCyan), + attrColor(termbox.ColorWhite), +} + +func upgrade(palette color.Palette) color.Palette { + for i := 16; i <= 255; i++ { + palette = append(palette, attrColor(i)) + } + return palette +} + +type attrColor termbox.Attribute + +var base = [...]uint8{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff} + +func (c attrColor) RGBA() (r, g, b, a uint32) { + switch termbox.Attribute(c) { + case termbox.ColorBlack: + return 0, 0, 0, math.MaxUint16 + case termbox.ColorRed: + return math.MaxUint16, 0, 0, math.MaxUint16 + case termbox.ColorGreen: + return 0, math.MaxUint16, 0, math.MaxUint16 + case termbox.ColorYellow: + return math.MaxUint16, math.MaxUint16, 0, math.MaxUint16 + case termbox.ColorBlue: + return 0, 0, math.MaxUint16, math.MaxUint16 + case termbox.ColorMagenta: + return math.MaxUint16, 0, math.MaxUint16, math.MaxUint16 + case termbox.ColorCyan: + return 0, math.MaxUint16, math.MaxUint16, math.MaxUint16 + case termbox.ColorWhite: + return math.MaxUint16, math.MaxUint16, math.MaxUint16, math.MaxUint16 + } + + switch { + case c >= 16 && c <= 231: + c -= 16 + rgba := color.RGBA{R: base[(c/36)%6], G: base[(c/6)%6], B: base[c%6], A: 0xff} + return rgba.RGBA() + case c >= 232 && c <= 255: + x := uint8(0x08 + 0xa*(c-232)) + rgba := color.RGBA{R: x, G: x, B: x, A: 0xff} + return rgba.RGBA() + } + + panic("not found") +} + +func fit(w, h, iw, ih int) (sw, sh int) { + if w >= iw && h >= ih { + // image is smaller than screen + return iw, ih + } else if w >= iw { + // image is taller than the screen + return iw * h / ih, h + } else if h >= ih { + // image is skinnier than the screen + return w, ih * w / iw + } else { + sw, sh = iw*h/ih, h + if sw <= w { + return sw, sh + } + return w, ih * w / iw + } +} + +func draw(img image.Image) { + w, h := termbox.Size() + iw, ih := img.Bounds().Max.X, img.Bounds().Max.Y + sw, sh := fit(w, h, iw*2, ih) + termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + if x >= sw || y >= sh { + termbox.SetCell(x, y, ' ', termbox.ColorDefault, termbox.ColorDefault) + continue + } + xi := x * img.Bounds().Max.X / sw + yi := y * img.Bounds().Max.Y / sh + a := palette.Convert(img.At(xi, yi)) + if at, ok := a.(attrColor); ok { + termbox.SetCell(x, y, ' ', termbox.ColorDefault, + termbox.Attribute(at)) + } + } + } + termbox.Flush() +} + +func main() { + fname := os.Args[1] + + f, err := os.Open(fname) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + img, _, err := image.Decode(f) + if err != nil { + log.Fatal(err) + } + + err = termbox.Init() + if err != nil { + panic(err) + } + defer termbox.Close() + + if termbox.SetOutputMode(termbox.Output256) == termbox.Output256 { + palette = upgrade(palette) + } + + lw, lh := termbox.Size() + draw(img) + termbox.PollEvent() + termbox.Flush() + if w, h := termbox.Size(); lw != w || lh != h { + lw, lh = w, h + draw(img) + } +} diff --git a/testdata/tiv.sh b/testdata/tiv.sh index d467656..1063dd5 100644 --- a/testdata/tiv.sh +++ b/testdata/tiv.sh @@ -13,6 +13,8 @@ # Unreadable: 1×2 (high resolution, slowest rendering, without double horizontal resolution) char_w=5 char_h=8 +char_w=1 +char_h=2 # If file doesn't exist, then die with 'No such file or directory' exit code if [ ! -f "$1" ] diff --git a/testdata/tofugu.d/a-hiragana-0.sm.png b/testdata/tofugu.d/a-hiragana-0.sm.png new file mode 100644 index 0000000000000000000000000000000000000000..1fe4ee63a08d378f50e4e0dcae77360bddc32480 GIT binary patch literal 2031 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt00(qQO+^Ri1O)>a25j|IT>tbRL=%5BsWw&(sYX%L_#mnNGp49NOl)FeH8s_m zf=!zmZI#4#Of01Y1A$^G!tfXd=$(7-*?WEdIInwW2CDnV&AsRD^{w}})&b`_MjEej z%&eDR+6j1vxulJDRe73O43T>_0Yc0?6q- zKVdSU>WXGC=NuLU|D(qiBVA%6i{jXQBUeEnP+L&p2tl0rTXkjpKK5%6LMWsNr8KSr zkkVEP1T9U2n2P4NjzpEl6$;HDuDsZ10i|@N__P>-s4l*h1dE48QsG2_3-g>5s!9MN zB8Wgm2pZdkPjvZ8MleEEq)1Em`0;=I{_LLd;7XGV3&ctB#YpCQp|r&CcFlkU0NU*n zubnt{@W`ntAe$Vo!&1u0zkasiZHtG8G6fP`MCo^W>G0{v$@3GFXa95Z;^i&?AW^1$ zaV-F-#~%S&ORl|U#B_oE0XYFM$oANm1XU20tJ%dO?lpjbsWRb0R;dYlAP0h2DxH0I&f~%qY%m7@>rt49`lyYj{{uiE3Qvmx zXGCTK5K7GUKub{?K+phScz)KJVF3Zx=amHZ$rUGQp*6H{+47akmRK2-04ynO@U8O$981`8hOik=z$~`;;!^Yr!}BT- zeyOZ?RUlCXpgxsl6CsoUW@eq~$qi-pG)*f-_<6V-UmYt#AU=8MCbY6LxY*539Rss2 zcU1nXuPw!sZ1esK81LnYJ1YiKZ0>ce$1g1}04(p>U%W3G+hfc5 zxVivv_!b{RT13|4Np?d#J62DmUCfPC2$b?za0Wm$u6Sa>g=*S>FMK~OXfX-fw&$KQ zyr^#Pb7e39^XGnpY6yOJMQ>hKXo>Rtr%F~;Cg1h|W8G-1z8PbWf0W9uTk;TiTOIhO zc)!3vhB&dHbRofx(glEgnH5pq>y5m&yku1qNWS(PLC7F~ovhcgR-r^7ko~1Z!25}a zv6?n3es{a)n~Few&@k#M7R~{{M0H;mi>i#H%yz+JNz1-^N6+{2cJ}!5Nf{NxItZHC zRUe9Q`M`R~r1OzoJvD~mKYi9!4#cHv3I|Dcd2d{h80Ad0d5d7I?n2L)--Cj!M&?6L&t@rxyZD7Jq2bL;vz zLA_gkfaLjSJ~e-cQ`sSP5H-yG?t zWg%IQhlc9=S(f(kqIl;E25`=4{XD@ZtP^5QC?VX5Yz{7WDUdoim$(Mgsis(WCg#>q zTFzf1oeiKFKKeE23`$nXu=Fqk>&awh(e5rQvtK{(Ks#)d8EcfLSbxyk?4L{)u_LW%j%_5*7cue@RX4c99W)Xm6h$uCtUv!K@I z|0tSY9`yhaa$gMvnFVl#Ll6+5MT$6>8(i_;9H6%C$}iGywyjgZLY|nd9>9MA