mirror of
https://github.com/gizak/termui.git
synced 2025-04-26 13:48:54 +08:00
Merge 5330efee94e37e98c0e363b724d5fc1168bc6660 into 2b8f0c7960e9553acea6d579a740713066da5e13
This commit is contained in:
commit
5d2f0f4d04
5014
_examples/image_pixel.go
Normal file
5014
_examples/image_pixel.go
Normal file
File diff suppressed because it is too large
Load Diff
7
block.go
7
block.go
@ -27,6 +27,8 @@ type Block struct {
|
|||||||
Title string
|
Title string
|
||||||
TitleStyle Style
|
TitleStyle Style
|
||||||
|
|
||||||
|
ANSIString string
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,3 +105,8 @@ func (self *Block) SetRect(x1, y1, x2, y2 int) {
|
|||||||
func (self *Block) GetRect() image.Rectangle {
|
func (self *Block) GetRect() image.Rectangle {
|
||||||
return self.Rectangle
|
return self.Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetANSIString implements the Drawable interface.
|
||||||
|
func (self *Block) GetANSIString() string {
|
||||||
|
return self.ANSIString
|
||||||
|
}
|
||||||
|
35
render.go
35
render.go
@ -5,6 +5,7 @@
|
|||||||
package termui
|
package termui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -16,10 +17,44 @@ type Drawable interface {
|
|||||||
SetRect(int, int, int, int)
|
SetRect(int, int, int, int)
|
||||||
Draw(*Buffer)
|
Draw(*Buffer)
|
||||||
sync.Locker
|
sync.Locker
|
||||||
|
GetANSIString() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Render(items ...Drawable) {
|
func Render(items ...Drawable) {
|
||||||
|
// draw background, etc for items with ANSI escape strings
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
|
if len(item.GetANSIString()) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf := NewBuffer(item.GetRect())
|
||||||
|
item.Lock()
|
||||||
|
item.Draw(buf)
|
||||||
|
item.Unlock()
|
||||||
|
for point, cell := range buf.CellMap {
|
||||||
|
if point.In(buf.Rectangle) {
|
||||||
|
tb.SetCell(
|
||||||
|
point.X, point.Y,
|
||||||
|
cell.Rune,
|
||||||
|
tb.Attribute(cell.Style.Fg+1)|tb.Attribute(cell.Style.Modifier), tb.Attribute(cell.Style.Bg+1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tb.Flush()
|
||||||
|
|
||||||
|
// draw images, etc over the already filled cells with ANSI escape strings (sixel, ...)
|
||||||
|
for _, item := range items {
|
||||||
|
if ansiString := item.GetANSIString(); len(ansiString) > 0 {
|
||||||
|
fmt.Printf("%s", ansiString)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw items without ANSI strings last in case the ANSI escape strings ended messed up
|
||||||
|
for _, item := range items {
|
||||||
|
if len(item.GetANSIString()) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
buf := NewBuffer(item.GetRect())
|
buf := NewBuffer(item.GetRect())
|
||||||
item.Lock()
|
item.Lock()
|
||||||
item.Draw(buf)
|
item.Draw(buf)
|
||||||
|
19
widgets/exp/HACKING.md
Normal file
19
widgets/exp/HACKING.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
possible enhancements:
|
||||||
|
|
||||||
|
kitty https://sw.kovidgoyal.net/kitty/graphics-protocol.html
|
||||||
|
|
||||||
|
Terminology (from Enlightenment) https://www.enlightenment.org/docs/apps/terminology.md#tycat https://github.com/billiob/terminology
|
||||||
|
|
||||||
|
urxvt pixbuf / ...
|
||||||
|
|
||||||
|
Tektronix 4014
|
||||||
|
|
||||||
|
ReGis
|
||||||
|
|
||||||
|
reimplement 23imgdisplay:
|
||||||
|
|
||||||
|
check for X11 and not Alacritty (https://github.com/jwilm/alacritty/issues/1021)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
if i remember correctly xterm has a size limit for sixel images - pixel width???
|
0
widgets/exp/README.md
Normal file
0
widgets/exp/README.md
Normal file
36
widgets/exp/aaa_init.go
Normal file
36
widgets/exp/aaa_init.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// <Copyright> 2018,2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/gizak/termui/v3"
|
||||||
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// the file name should appear at the top when alphabetically sorted (start with "aaa")
|
||||||
|
// because the init() functions are executed in alphabetic file order
|
||||||
|
func init() {
|
||||||
|
scanTerminal()
|
||||||
|
var drawFallback func(*widgets.Image, *Buffer) (error)
|
||||||
|
if drbl, ok := widgets.GetDrawers()["block"]; ok {
|
||||||
|
drawFallback = drbl.Draw
|
||||||
|
}
|
||||||
|
widgets.RegisterDrawer(
|
||||||
|
"block",
|
||||||
|
widgets.Drawer{
|
||||||
|
Remote: true,
|
||||||
|
IsEscapeString: false,
|
||||||
|
Available: func() bool {return true},
|
||||||
|
Draw: func(img *widgets.Image, buf *Buffer) (err error) {
|
||||||
|
// possible reattachments of the terminal multiplexer?
|
||||||
|
if isMuxed {
|
||||||
|
scanTerminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
return drawFallback(img, buf)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
60
widgets/exp/drawer_iterm2.go
Normal file
60
widgets/exp/drawer_iterm2.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// <Copyright> 2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"image/png"
|
||||||
|
|
||||||
|
. "github.com/gizak/termui/v3"
|
||||||
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
widgets.RegisterDrawer(
|
||||||
|
"iterm2",
|
||||||
|
widgets.Drawer{
|
||||||
|
Remote: true,
|
||||||
|
IsEscapeString: true,
|
||||||
|
Available: func() bool {return isIterm2 || isMacTerm},
|
||||||
|
Draw: drawITerm2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawITerm2(wdgt *widgets.Image, buf *Buffer) (err error) {
|
||||||
|
wdgt.Block.Draw(buf)
|
||||||
|
|
||||||
|
img, changed, err := resizeImage(wdgt, buf)
|
||||||
|
if !changed || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageDimensions := wdgt.GetVisibleArea()
|
||||||
|
|
||||||
|
// https://www.iterm2.com/documentation-images.html
|
||||||
|
if isIterm2 || isMacTerm {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err = png.Encode(buf, img); err != nil {
|
||||||
|
goto skipIterm2
|
||||||
|
}
|
||||||
|
imgBase64 := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
|
nameBase64 := base64.StdEncoding.EncodeToString([]byte(wdgt.Block.Title))
|
||||||
|
// 0 for stretching - 1 for no stretching
|
||||||
|
noStretch := 0
|
||||||
|
iterm2String := wrap(fmt.Sprintf("\033]1337;File=name=%s;inline=1;height=%d;width=%d;preserveAspectRatio=%d:%s\a", nameBase64, imageDimensions.Max.Y, nameBase64, imageDimensions.Max.X, noStretch, imgBase64))
|
||||||
|
// for width, height: "auto" || N: N character cells || Npx: N pixels || N%: N percent of terminal width/height
|
||||||
|
wdgt.Block.ANSIString = fmt.Sprintf("\033[%d;%dH%s", imageDimensions.Min.Y, imageDimensions.Min.X, iterm2String)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
skipIterm2:
|
||||||
|
|
||||||
|
return errors.New("no method applied for ANSI drawing")
|
||||||
|
}
|
102
widgets/exp/drawer_kitty.go
Normal file
102
widgets/exp/drawer_kitty.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// <Copyright> 2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"image/png"
|
||||||
|
|
||||||
|
. "github.com/gizak/termui/v3"
|
||||||
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kittyLimit = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO: for numbering of ids
|
||||||
|
kittyImageCount int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
widgets.RegisterDrawer(
|
||||||
|
"kitty",
|
||||||
|
widgets.Drawer{
|
||||||
|
Remote: true,
|
||||||
|
IsEscapeString: true,
|
||||||
|
Available: func() bool {return isKitty},
|
||||||
|
Draw: drawKitty,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawKitty(wdgt *widgets.Image, buf *Buffer) (err error) {
|
||||||
|
if !isKitty {
|
||||||
|
return errors.New("method not supported for this terminal type")
|
||||||
|
}
|
||||||
|
|
||||||
|
wdgt.Block.Draw(buf)
|
||||||
|
|
||||||
|
// TODO: FIX THIS
|
||||||
|
termWidth, termHeight := getTermSizeInChars(true)
|
||||||
|
var _ = termWidth
|
||||||
|
/*
|
||||||
|
if termWidth == 0 || termHeight == 0 {
|
||||||
|
return errors.New("could not query terminal dimensions")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
img, changed, err := resizeImage(wdgt, buf)
|
||||||
|
if !changed || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var imgHeight int
|
||||||
|
imageDimensions := wdgt.GetVisibleArea()
|
||||||
|
if wdgt.Inner.Max.Y < termHeight {
|
||||||
|
imgHeight = wdgt.Inner.Dy()
|
||||||
|
} else {
|
||||||
|
imgHeight = termHeight-1
|
||||||
|
}
|
||||||
|
imgHeight = wdgt.Inner.Dy() // TODO: REMOVE THIS CRUTCH
|
||||||
|
|
||||||
|
// https://sw.kovidgoyal.net/kitty/graphics-protocol.html#remote-client
|
||||||
|
// https://sw.kovidgoyal.net/kitty/graphics-protocol.html#png-data
|
||||||
|
// https://sw.kovidgoyal.net/kitty/graphics-protocol.html#controlling-displayed-image-layout
|
||||||
|
bytBuf := new(bytes.Buffer)
|
||||||
|
if err = png.Encode(bytBuf, img); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
imgBase64 := base64.StdEncoding.EncodeToString(bytBuf.Bytes())
|
||||||
|
lenImgB64 := len([]byte(imgBase64))
|
||||||
|
// a=T action
|
||||||
|
// t=d payload is (base64 encoded) data itself not a file location
|
||||||
|
// f=100 format: 100 = PNG payload
|
||||||
|
// o=z data compression
|
||||||
|
// X=...,Y=,,, Upper left image corner in cell coordinates (starting with 1, 1)
|
||||||
|
// c=...,r=... image size in cell columns and rows
|
||||||
|
// w=...,h=... width & height (in pixels) of the image area to display // TODO: Use this to let Kitty handle cropping!
|
||||||
|
// z=0 z-index vertical stacking order of the image
|
||||||
|
// m=[01] 0 last escape code chunk - 1 for all except the last
|
||||||
|
var kittyString string
|
||||||
|
var zIndex = 2 // draw over text
|
||||||
|
settings := fmt.Sprintf("a=T,t=d,f=100,X=%d,Y=%d,c=%d,r=%d,z=%d,", imageDimensions.Min.X, imageDimensions.Min.Y, wdgt.Inner.Dx(), imgHeight, zIndex)
|
||||||
|
i := 0
|
||||||
|
for ; i < (lenImgB64-1)/kittyLimit; i++ {
|
||||||
|
kittyString += wrap(fmt.Sprintf("\033_G%sm=1;%s\033\\", settings, imgBase64[i*kittyLimit:(i+1)*kittyLimit]))
|
||||||
|
settings = ""
|
||||||
|
}
|
||||||
|
kittyString += wrap(fmt.Sprintf("\033_G%sm=0;%s\033\\", settings, imgBase64[i*kittyLimit:lenImgB64]))
|
||||||
|
|
||||||
|
wdgt.Block.ANSIString = fmt.Sprintf("\033[%d;%dH%s", imageDimensions.Min.Y, imageDimensions.Min.X, kittyString)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// store images with ids in Kitty
|
63
widgets/exp/drawer_sixel.go
Normal file
63
widgets/exp/drawer_sixel.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// <Copyright> 2018,2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/mattn/go-sixel"
|
||||||
|
|
||||||
|
. "github.com/gizak/termui/v3"
|
||||||
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
widgets.RegisterDrawer(
|
||||||
|
"sixel",
|
||||||
|
widgets.Drawer{
|
||||||
|
Remote: true,
|
||||||
|
IsEscapeString: true,
|
||||||
|
Available: func() bool {return sixelCapable},
|
||||||
|
Draw: drawSixel,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawSixel(wdgt *widgets.Image, buf *Buffer) (err error) {
|
||||||
|
wdgt.Block.Draw(buf)
|
||||||
|
|
||||||
|
img, changed, err := resizeImage(wdgt, buf)
|
||||||
|
if !changed || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// sixel
|
||||||
|
// https://vt100.net/docs/vt3xx-gp/chapter14.html
|
||||||
|
if sixelCapable {
|
||||||
|
byteBuf := new(bytes.Buffer)
|
||||||
|
enc := sixel.NewEncoder(byteBuf)
|
||||||
|
enc.Dither = true
|
||||||
|
if err := enc.Encode(img); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sixelString := wrap("\033[?8452h" + byteBuf.String())
|
||||||
|
// position where the image should appear (upper left corner) + sixel
|
||||||
|
// https://github.com/mintty/mintty/wiki/CtrlSeqs#sixel-graphics-end-position
|
||||||
|
// "\033[?8452h" sets the cursor next right to the bottom of the image instead of below
|
||||||
|
// this prevents vertical scrolling when the image fills the last line.
|
||||||
|
// horizontal scrolling because of this did not happen in my test cases.
|
||||||
|
// "\033[?80l" disables sixel scrolling if it isn't already.
|
||||||
|
wdgt.Block.ANSIString = fmt.Sprintf("\033[%d;%dH%s", wdgt.Inner.Min.Y + 1, wdgt.Inner.Min.X + 1, sixelString)
|
||||||
|
// test string "HI"
|
||||||
|
// wdgt.Block.ANSIString = fmt.Sprintf("\033[%d;%dH\033[?8452h%s", wdgt.Inner.Min.Y+1, wdgt.Inner.Min.X+1, "\033Pq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\033\\")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("no method applied for ANSI drawing")
|
||||||
|
}
|
90
widgets/exp/drawer_urxvt.go
Normal file
90
widgets/exp/drawer_urxvt.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// <Copyright> 2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"image/png"
|
||||||
|
"crypto/md5"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
. "github.com/gizak/termui/v3"
|
||||||
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
tempdir string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
widgets.RegisterDrawer(
|
||||||
|
"urxvt",
|
||||||
|
widgets.Drawer{
|
||||||
|
Remote: false,
|
||||||
|
IsEscapeString: true,
|
||||||
|
Available: func() bool {return isUrxvt},
|
||||||
|
Draw: drawUrxvt,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawUrxvt(wdgt *widgets.Image, buf *Buffer) (err error) {
|
||||||
|
if !isUrxvt {
|
||||||
|
return errors.New("method not supported for this terminal type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// wdgt.Block.Draw(buf)
|
||||||
|
|
||||||
|
var widthPercentage, heightPercentage, CenterPosXPercentage, CenterPosYPercentage int
|
||||||
|
termWidth, termHeight := getTermSizeInChars(true)
|
||||||
|
if termWidth == 0 || termHeight == 0 {
|
||||||
|
return errors.New("could not query terminal dimensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
widthPercentage = (100*wdgt.Inner.Dx())/termWidth
|
||||||
|
heightPercentage = (100*wdgt.Inner.Dy())/termHeight
|
||||||
|
maxX := wdgt.Inner.Max.X
|
||||||
|
maxY := wdgt.Inner.Max.Y
|
||||||
|
if termWidth < maxX {
|
||||||
|
maxX = termWidth
|
||||||
|
}
|
||||||
|
if termHeight < maxY {
|
||||||
|
maxY = termHeight
|
||||||
|
}
|
||||||
|
CenterPosXPercentage = 50*(wdgt.Inner.Min.X+maxX)/termWidth
|
||||||
|
CenterPosYPercentage = 50*(wdgt.Inner.Min.Y+maxY)/termHeight
|
||||||
|
|
||||||
|
img, changed, err := resizeImage(wdgt, buf)
|
||||||
|
if !changed || err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bytBuf := new(bytes.Buffer)
|
||||||
|
if err = png.Encode(bytBuf, img); err != nil {
|
||||||
|
return errors.New("image encoding failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi, err := os.Stat(tempdir); err != nil || !fi.IsDir() {
|
||||||
|
if tempdir, err = ioutil.TempDir("", "termui."); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer os.RemoveAll(dir) // clean up
|
||||||
|
|
||||||
|
filename := filepath.Join(tempdir, fmt.Sprintf("urxvt-%x", md5.Sum(bytBuf.Bytes())) + ".png")
|
||||||
|
if err := ioutil.WriteFile(filename, bytBuf.Bytes(), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// "op=keep-aspect" maintains the image aspect ratio when scaling
|
||||||
|
wdgt.Block.ANSIString = wrap(fmt.Sprintf("\033]20;%s;%dx%d+%d+%d:op=keep-aspect\a", filename, widthPercentage, heightPercentage, CenterPosXPercentage, CenterPosYPercentage))
|
||||||
|
return nil
|
||||||
|
}
|
107
widgets/exp/image.go
Normal file
107
widgets/exp/image.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// <Copyright> 2018,2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
|
||||||
|
. "github.com/gizak/termui/v3"
|
||||||
|
"github.com/gizak/termui/v3/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
charBoxWidthInPixels, charBoxHeightInPixels float64
|
||||||
|
charBoxWidthColumns, charBoxHeightRows int
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func resizeImage(wdgt *widgets.Image, buf *Buffer) (img image.Image, changed bool, err error) {
|
||||||
|
img = wdgt.Image
|
||||||
|
// img = image.NRGBA{}
|
||||||
|
|
||||||
|
// get dimensions //
|
||||||
|
// terminal size measured in cells
|
||||||
|
imageWidthInColumns := wdgt.Inner.Dx()
|
||||||
|
imageHeightInRows := wdgt.Inner.Dy()
|
||||||
|
|
||||||
|
// terminal size in cells and pixels and calculated terminal character box size in pixels
|
||||||
|
var termWidthInColumns, termHeightInRows int
|
||||||
|
var charBoxWidthInPixelsTemp, charBoxHeightInPixelsTemp float64
|
||||||
|
termWidthInColumns, termHeightInRows, _, _, charBoxWidthInPixelsTemp, charBoxHeightInPixelsTemp, err = getTermSize()
|
||||||
|
if err != nil {
|
||||||
|
return img, true, err
|
||||||
|
}
|
||||||
|
// update if value is more precise
|
||||||
|
if termWidthInColumns > charBoxWidthColumns {
|
||||||
|
charBoxWidthInPixels = charBoxWidthInPixelsTemp
|
||||||
|
}
|
||||||
|
if termHeightInRows > charBoxHeightRows {
|
||||||
|
charBoxHeightInPixels = charBoxHeightInPixelsTemp
|
||||||
|
}
|
||||||
|
if isTmux {charBoxWidthInPixels, charBoxHeightInPixels = 10, 19} // mlterm settings (temporary)
|
||||||
|
|
||||||
|
// calculate image size in pixels
|
||||||
|
// subtract 1 pixel for small deviations from char box size (float64)
|
||||||
|
imageWidthInPixels := int(float64(imageWidthInColumns) * charBoxWidthInPixels) - 1
|
||||||
|
imageHeightInPixels := int(float64(imageHeightInRows) * charBoxHeightInPixels) - 1
|
||||||
|
if imageWidthInPixels == 0 || imageHeightInPixels == 0 {
|
||||||
|
return img, true, errors.New("could not calculate the image size in pixels")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle only partially displayed image
|
||||||
|
// otherwise we get scrolling
|
||||||
|
var needsCropX, needsCropY bool
|
||||||
|
var imgCroppedWidth, imgCroppedHeight int
|
||||||
|
imgCroppedWidth = imageWidthInPixels
|
||||||
|
imgCroppedHeight = imageHeightInPixels
|
||||||
|
if wdgt.Max.Y >= int(termHeightInRows) {
|
||||||
|
var scrollExtraRows int
|
||||||
|
// remove last 2 rows for xterm when cropped vertically to prevent scrolling
|
||||||
|
if isXterm {
|
||||||
|
scrollExtraRows = 2
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if isKitty {
|
||||||
|
scrollExtraRows = 1
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// subtract 1 pixel for small deviations from char box size (float64)
|
||||||
|
imgCroppedHeight = int(float64(int(termHeightInRows) - wdgt.Inner.Min.Y - scrollExtraRows) * charBoxHeightInPixels) - 1
|
||||||
|
needsCropY = true
|
||||||
|
}
|
||||||
|
if wdgt.Max.X >= int(termWidthInColumns) {
|
||||||
|
var scrollExtraColumns int
|
||||||
|
imgCroppedWidth = int(float64(int(termWidthInColumns) - wdgt.Inner.Min.X - scrollExtraColumns) * charBoxWidthInPixels) - 1
|
||||||
|
needsCropX = true
|
||||||
|
}
|
||||||
|
|
||||||
|
lastImageDimensions := wdgt.GetVisibleArea()
|
||||||
|
// this is meant for comparison and for positioning in the ANSI string
|
||||||
|
// the Min values are in cells while the Max values are in pixels
|
||||||
|
// imageDimensions := image.Rectangle{Min: image.Point{X: 0, Y: 0}, Max: image.Point{X: imgCroppedWidth, Y: imgCroppedHeight}}
|
||||||
|
imageDimensions := image.Rectangle{Min: image.Point{X: wdgt.Inner.Min.X + 1, Y: wdgt.Inner.Min.Y + 1}, Max: image.Point{X: imgCroppedWidth, Y: imgCroppedHeight}}
|
||||||
|
wdgt.SetVisibleArea(imageDimensions)
|
||||||
|
// print saved ANSI string if image size and position didn't change
|
||||||
|
if imageDimensions.Min.X == lastImageDimensions.Min.X && imageDimensions.Min.Y == lastImageDimensions.Min.Y && imageDimensions.Max.X == lastImageDimensions.Max.X && imageDimensions.Max.Y == lastImageDimensions.Max.Y {
|
||||||
|
// reuse last encoded image because of unchanged image dimensions
|
||||||
|
return img, false, nil
|
||||||
|
}
|
||||||
|
lastImageDimensions = imageDimensions
|
||||||
|
|
||||||
|
// resize and crop the image //
|
||||||
|
img = imaging.Resize(wdgt.Image, imageWidthInPixels, imageHeightInPixels, imaging.Lanczos)
|
||||||
|
if needsCropX || needsCropY {
|
||||||
|
img = imaging.Crop(img, image.Rectangle{Min: image.Point{X: 0, Y: 0}, Max: image.Point{X: imgCroppedWidth, Y: imgCroppedHeight}})
|
||||||
|
}
|
||||||
|
if img.Bounds().Dx() == 0 || img.Bounds().Dy() == 0 {
|
||||||
|
return img, true, fmt.Errorf("image size in pixels is 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, true, err
|
||||||
|
}
|
28
widgets/exp/multiplexer.go
Normal file
28
widgets/exp/multiplexer.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// <Copyright> 2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func wrap(s string) string {
|
||||||
|
if !isMuxed {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if isTmux {
|
||||||
|
return tmuxWrap(s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func tmuxWrap(s string) string {
|
||||||
|
return "\033Ptmux;" + strings.Replace(s, "\033", "\033\033", -1) + "\033\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// https://savannah.gnu.org/bugs/index.php?56063
|
||||||
|
func screenWrap(s string) string {}
|
||||||
|
*/
|
177
widgets/exp/terminal_query.go
Normal file
177
widgets/exp/terminal_query.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// <Copyright> 2018,2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattn/go-tty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTermSize() (termWidthInColumns, termHeightInRows, termWidthInPixels, termHeightInPixels int, charBoxWidthInPixels, charBoxHeightInPixels float64, err error) {
|
||||||
|
// this uses a combination of TIOCGWINSZ and \033[14t , \033[18t
|
||||||
|
// the syscall to TIOCGWINSZ only works locally
|
||||||
|
|
||||||
|
var cx, cy, px, py int
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
t, err := tty.Open()
|
||||||
|
defer t.Close()
|
||||||
|
|
||||||
|
cx, cy, px, py, err = t.SizePixel()
|
||||||
|
// if isMuxed {
|
||||||
|
if false { // temporary
|
||||||
|
// in case of split view it is better to query from the same source
|
||||||
|
/*if cx <= 0 || cy <= 0 || px <= 0 || py <= 0 {
|
||||||
|
cx, cy = getTermSizeInChars(true)
|
||||||
|
px, py = getTermSizeInPixels(true)
|
||||||
|
if cx <= 0 || cy <= 0 || px <= 0 || py <= 0 {
|
||||||
|
cx, cy = getTermSizeInChars(false)
|
||||||
|
px, py = getTermSizeInPixels(false)
|
||||||
|
if cx <= 0 || cy <= 0 || px <= 0 || py <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
} else if err == nil {
|
||||||
|
if cx > 0 && cy > 0 {
|
||||||
|
if px <= 0 || py <= 0 {
|
||||||
|
px, py = getTermSizeInPixels(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cx, cy = getTermSizeInChars(true); cx != 0 && cy != 0 {
|
||||||
|
if px <= 0 || py <= 0 {
|
||||||
|
px, py = getTermSizeInPixels(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
termWidthInColumns = cx
|
||||||
|
termHeightInRows = cy
|
||||||
|
termWidthInPixels = px
|
||||||
|
termHeightInPixels = py
|
||||||
|
charBoxWidthInPixels = float64(px) / float64(cx)
|
||||||
|
charBoxHeightInPixels = float64(py) / float64(cy)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTermSizeInChars(needsWrap bool) (x, y int) {
|
||||||
|
// query terminal size in character boxes
|
||||||
|
// answer: <termHeightInRows>;<termWidthInColumns>t
|
||||||
|
s := "\033[18t"
|
||||||
|
if needsWrap {
|
||||||
|
s = wrap(s)
|
||||||
|
}
|
||||||
|
q := queryTerm(s)
|
||||||
|
if len(q) != 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
||||||
|
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
||||||
|
x = xx
|
||||||
|
y = yy
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTermSizeInPixels(needsWrap bool) (x, y int) {
|
||||||
|
// query terminal size in pixels
|
||||||
|
// answer: <termHeightInPixels>;<termWidthInPixels>t
|
||||||
|
s := "\033[14t"
|
||||||
|
if needsWrap {
|
||||||
|
s = wrap(s)
|
||||||
|
}
|
||||||
|
q := queryTerm(s)
|
||||||
|
|
||||||
|
if len(q) != 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if yy, err := strconv.Atoi(string(q[1])); err == nil {
|
||||||
|
if xx, err := strconv.Atoi(string(q[2])); err == nil {
|
||||||
|
x = xx
|
||||||
|
y = yy
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryTerm(qs string) (ret [][]rune) {
|
||||||
|
// temporary fix for xterm - not completely sure if still needed
|
||||||
|
// otherwise TUI wouldn't react to any further events
|
||||||
|
// resizing still works though
|
||||||
|
if isXterm && qs != "\033[0c" && qs != wrap("\033[0c") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var b []rune
|
||||||
|
|
||||||
|
t, err := tty.Open()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer t.Close()
|
||||||
|
// query terminal
|
||||||
|
fmt.Printf(qs)
|
||||||
|
|
||||||
|
for {
|
||||||
|
r, err := t.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// handle key event
|
||||||
|
switch r {
|
||||||
|
case 'c', 't':
|
||||||
|
ret = append(ret, b)
|
||||||
|
goto afterLoop
|
||||||
|
case '?', ';':
|
||||||
|
ret = append(ret, b)
|
||||||
|
b = []rune{}
|
||||||
|
default:
|
||||||
|
b = append(b, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterLoop:
|
||||||
|
ch <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
var timer *time.Timer
|
||||||
|
if isTmux {
|
||||||
|
// tmux needs a bit more time
|
||||||
|
timer = time.NewTimer(50000 * time.Microsecond)
|
||||||
|
} else {
|
||||||
|
// on my system the terminals mlterm, xterm need at least around 100 microseconds
|
||||||
|
timer = time.NewTimer(500 * time.Microsecond)
|
||||||
|
}
|
||||||
|
defer timer.Stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
defer close(ch)
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
51
widgets/exp/terminal_scan.go
Normal file
51
widgets/exp/terminal_scan.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// <Copyright> 2018,2019 Simon Robin Lehn. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT license that can
|
||||||
|
// be found in the LICENSE file.
|
||||||
|
|
||||||
|
package exp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sixelCapable, isXterm, isMlterm, isMintty, isIterm2, isUrxvt, isAlacritty, isYaft, isKitty, isMacTerm, isTmux, isScreen, isMuxed bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func scanTerminal() {
|
||||||
|
if len(os.Getenv("XTERM_VERSION")) > 0 { isXterm = true } else { isXterm = false }
|
||||||
|
if os.Getenv("TERM_PROGRAM") == "iTerm.app" { isIterm2 = true } else { isIterm2 = false } // https://superuser.com/a/683971
|
||||||
|
if os.Getenv("TERM_PROGRAM") == "MacTerm" { isMacTerm = true } else { isMacTerm = false } // https://github.com/kmgrant/macterm/issues/3#issuecomment-458387953
|
||||||
|
if strings.HasPrefix(os.Getenv("TERM"), "rxvt-unicode") { isUrxvt = true } else { isUrxvt = false }
|
||||||
|
if os.Getenv("TERM") == "xterm-kitty" ||len(os.Getenv("KITTY_WINDOW_ID")) > 0 { isKitty = true } else { isKitty = false }
|
||||||
|
if len(os.Getenv("MLTERM")) > 0 { isMlterm = true } else { isMlterm = false }
|
||||||
|
if len(os.Getenv("MINTTY_SHORTCUT")) > 0 { isMintty = true } else { isMintty = false }
|
||||||
|
if len(os.Getenv("ALACRITTY_LOG")) > 0 { isAlacritty = true } else { isAlacritty = false }
|
||||||
|
if os.Getenv("TERM") == "yaft-256color" { isYaft = true } else { isYaft = false } // https://github.com/uobikiemukot/yaft/blob/21b69124a2907ad6ede8f45ca96c390615e3dc0c/conf.h#L26
|
||||||
|
if strings.HasPrefix(os.Getenv("TERM"), "screen") && len(os.Getenv("STY")) > 0 { isScreen = true } else { isScreen = false }
|
||||||
|
if (strings.HasPrefix(os.Getenv("TERM"), "screen") || strings.HasPrefix(os.Getenv("TERM"), "tmux")) &&
|
||||||
|
len(os.Getenv("TMUX")) > 0 || len(os.Getenv("TMUX_PANE")) > 0 { isTmux = true } else { isTmux = false }
|
||||||
|
if isTmux || isScreen { isMuxed = true } else { isMuxed = false }
|
||||||
|
|
||||||
|
if isYaft {
|
||||||
|
sixelCapable = true
|
||||||
|
} else {
|
||||||
|
sixelCapable = false
|
||||||
|
}
|
||||||
|
// example query: "\033[0c"
|
||||||
|
// possible answer from the terminal (here xterm): "\033[[?63;1;2;4;6;9;15;22c", vte(?): ...62,9;c
|
||||||
|
// the "4" signals that the terminal is capable of sixel
|
||||||
|
// conhost.exe knows this sequence.
|
||||||
|
if !sixelCapable {
|
||||||
|
termCapabilities := queryTerm(wrap("\033[0c"))
|
||||||
|
for i, cap := range termCapabilities {
|
||||||
|
if i == 0 || i == len(termCapabilities) - 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(cap) == `4` {
|
||||||
|
sixelCapable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
widgets/image.go
123
widgets/image.go
@ -7,39 +7,107 @@ package widgets
|
|||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
. "github.com/gizak/termui/v3"
|
. "github.com/gizak/termui/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Drawer struct {
|
||||||
|
Remote bool // if the drawer can be used for remote terminals
|
||||||
|
IsEscapeString bool
|
||||||
|
Available func() (bool)
|
||||||
|
Draw func(*Image, *Buffer) (error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
drawersMu sync.Mutex
|
||||||
|
atomicDrawers atomic.Value
|
||||||
|
)
|
||||||
|
|
||||||
|
// from https://golang.org/src/image/format.go?s=1069:1193#L31
|
||||||
|
func RegisterDrawer(nameNew string, drawerNew Drawer) {
|
||||||
|
drawersMu.Lock()
|
||||||
|
// drawers, _ := atomicDrawers.Load().([]Drawer)
|
||||||
|
// atomicDrawers.Store(append(drawers, dr))
|
||||||
|
drawers, _ := atomicDrawers.Load().(map[string]Drawer)
|
||||||
|
drawersNew := make(map[string]Drawer)
|
||||||
|
for name, drawer := range drawers {
|
||||||
|
drawersNew[name] = drawer
|
||||||
|
}
|
||||||
|
drawersNew[nameNew] = drawerNew
|
||||||
|
atomicDrawers.Store(drawersNew)
|
||||||
|
drawersMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDrawers() map[string]Drawer {
|
||||||
|
if drawers, ok := atomicDrawers.Load().(map[string]Drawer); ok {
|
||||||
|
return drawers
|
||||||
|
}
|
||||||
|
return map[string]Drawer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterDrawer(
|
||||||
|
"block",
|
||||||
|
Drawer{
|
||||||
|
Remote: true,
|
||||||
|
IsEscapeString: false,
|
||||||
|
Available: func() bool {return true},
|
||||||
|
Draw: drawBlocks,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Block
|
Block
|
||||||
Image image.Image
|
Image image.Image
|
||||||
Monochrome bool
|
Monochrome bool
|
||||||
MonochromeThreshold uint8
|
MonochromeThreshold uint8
|
||||||
MonochromeInvert bool
|
MonochromeInvert bool
|
||||||
|
visibleSubImagePixels image.Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImage(img image.Image) *Image {
|
func NewImage(img image.Image) *Image {
|
||||||
return &Image{
|
return &Image{
|
||||||
Block: *NewBlock(),
|
Block: *NewBlock(),
|
||||||
MonochromeThreshold: 128,
|
MonochromeThreshold: 128,
|
||||||
Image: img,
|
Image: img,
|
||||||
|
visibleSubImagePixels: image.Rectangle{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Image) Draw(buf *Buffer) {
|
func (self *Image) Draw(buf *Buffer) {
|
||||||
self.Block.Draw(buf)
|
drawers := GetDrawers()
|
||||||
|
|
||||||
if self.Image == nil {
|
// fall back - draw with box characters atomicDrawers.Load().(map[string]Drawer)]["blocks"]
|
||||||
|
// possible enhancement: make use of further box characters like chafa:
|
||||||
|
// https://hpjansson.org/chafa/
|
||||||
|
// https://github.com/hpjansson/chafa/
|
||||||
|
if drbl, ok := drawers["block"]; ok {
|
||||||
|
drbl.Draw(self, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, dr := range drawers {
|
||||||
|
if name != "block" && dr.Available() {
|
||||||
|
dr.Draw(self, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawBlocks(img *Image, buf *Buffer) (err error) {
|
||||||
|
img.Block.Draw(buf)
|
||||||
|
|
||||||
|
if img.Image == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bufWidth := self.Inner.Dx()
|
bufWidth := img.Inner.Dx()
|
||||||
bufHeight := self.Inner.Dy()
|
bufHeight := img.Inner.Dy()
|
||||||
imageWidth := self.Image.Bounds().Dx()
|
imageWidth := img.Image.Bounds().Dx()
|
||||||
imageHeight := self.Image.Bounds().Dy()
|
imageHeight := img.Image.Bounds().Dy()
|
||||||
|
|
||||||
if self.Monochrome {
|
if img.Monochrome {
|
||||||
if bufWidth > imageWidth/2 {
|
if bufWidth > imageWidth/2 {
|
||||||
bufWidth = imageWidth / 2
|
bufWidth = imageWidth / 2
|
||||||
}
|
}
|
||||||
@ -48,33 +116,33 @@ func (self *Image) Draw(buf *Buffer) {
|
|||||||
}
|
}
|
||||||
for bx := 0; bx < bufWidth; bx++ {
|
for bx := 0; bx < bufWidth; bx++ {
|
||||||
for by := 0; by < bufHeight; by++ {
|
for by := 0; by < bufHeight; by++ {
|
||||||
ul := self.colorAverage(
|
ul := img.colorAverage(
|
||||||
2*bx*imageWidth/bufWidth/2,
|
2*bx*imageWidth/bufWidth/2,
|
||||||
(2*bx+1)*imageWidth/bufWidth/2,
|
(2*bx+1)*imageWidth/bufWidth/2,
|
||||||
2*by*imageHeight/bufHeight/2,
|
2*by*imageHeight/bufHeight/2,
|
||||||
(2*by+1)*imageHeight/bufHeight/2,
|
(2*by+1)*imageHeight/bufHeight/2,
|
||||||
)
|
)
|
||||||
ur := self.colorAverage(
|
ur := img.colorAverage(
|
||||||
(2*bx+1)*imageWidth/bufWidth/2,
|
(2*bx+1)*imageWidth/bufWidth/2,
|
||||||
(2*bx+2)*imageWidth/bufWidth/2,
|
(2*bx+2)*imageWidth/bufWidth/2,
|
||||||
2*by*imageHeight/bufHeight/2,
|
2*by*imageHeight/bufHeight/2,
|
||||||
(2*by+1)*imageHeight/bufHeight/2,
|
(2*by+1)*imageHeight/bufHeight/2,
|
||||||
)
|
)
|
||||||
ll := self.colorAverage(
|
ll := img.colorAverage(
|
||||||
2*bx*imageWidth/bufWidth/2,
|
2*bx*imageWidth/bufWidth/2,
|
||||||
(2*bx+1)*imageWidth/bufWidth/2,
|
(2*bx+1)*imageWidth/bufWidth/2,
|
||||||
(2*by+1)*imageHeight/bufHeight/2,
|
(2*by+1)*imageHeight/bufHeight/2,
|
||||||
(2*by+2)*imageHeight/bufHeight/2,
|
(2*by+2)*imageHeight/bufHeight/2,
|
||||||
)
|
)
|
||||||
lr := self.colorAverage(
|
lr := img.colorAverage(
|
||||||
(2*bx+1)*imageWidth/bufWidth/2,
|
(2*bx+1)*imageWidth/bufWidth/2,
|
||||||
(2*bx+2)*imageWidth/bufWidth/2,
|
(2*bx+2)*imageWidth/bufWidth/2,
|
||||||
(2*by+1)*imageHeight/bufHeight/2,
|
(2*by+1)*imageHeight/bufHeight/2,
|
||||||
(2*by+2)*imageHeight/bufHeight/2,
|
(2*by+2)*imageHeight/bufHeight/2,
|
||||||
)
|
)
|
||||||
buf.SetCell(
|
buf.SetCell(
|
||||||
NewCell(blocksChar(ul, ur, ll, lr, self.MonochromeThreshold, self.MonochromeInvert)),
|
NewCell(blocksChar(ul, ur, ll, lr, img.MonochromeThreshold, img.MonochromeInvert)),
|
||||||
image.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),
|
image.Pt(img.Inner.Min.X+bx, img.Inner.Min.Y+by),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +155,7 @@ func (self *Image) Draw(buf *Buffer) {
|
|||||||
}
|
}
|
||||||
for bx := 0; bx < bufWidth; bx++ {
|
for bx := 0; bx < bufWidth; bx++ {
|
||||||
for by := 0; by < bufHeight; by++ {
|
for by := 0; by < bufHeight; by++ {
|
||||||
c := self.colorAverage(
|
c := img.colorAverage(
|
||||||
bx*imageWidth/bufWidth,
|
bx*imageWidth/bufWidth,
|
||||||
(bx+1)*imageWidth/bufWidth,
|
(bx+1)*imageWidth/bufWidth,
|
||||||
by*imageHeight/bufHeight,
|
by*imageHeight/bufHeight,
|
||||||
@ -95,11 +163,22 @@ func (self *Image) Draw(buf *Buffer) {
|
|||||||
)
|
)
|
||||||
buf.SetCell(
|
buf.SetCell(
|
||||||
NewCell(c.ch(), NewStyle(c.fgColor(), ColorBlack)),
|
NewCell(c.ch(), NewStyle(c.fgColor(), ColorBlack)),
|
||||||
image.Pt(self.Inner.Min.X+bx, self.Inner.Min.Y+by),
|
image.Pt(img.Inner.Min.X+bx, img.Inner.Min.Y+by),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// measured in pixels
|
||||||
|
func (self *Image) SetVisibleArea(area image.Rectangle) {
|
||||||
|
self.visibleSubImagePixels = area
|
||||||
|
}
|
||||||
|
|
||||||
|
// measured in pixels
|
||||||
|
func (self *Image) GetVisibleArea() image.Rectangle {
|
||||||
|
return self.visibleSubImagePixels
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Image) colorAverage(x0, x1, y0, y1 int) colorAverager {
|
func (self *Image) colorAverage(x0, x1, y0, y1 int) colorAverager {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user