sixel working

This commit is contained in:
Liam Galvin 2018-10-27 22:06:26 +01:00
parent 0cc73f1b71
commit 20c3c0cb14
11 changed files with 426 additions and 2 deletions

View File

@ -1,8 +1,15 @@
package buffer
import (
"image"
"github.com/go-gl/gl/all-core/gl"
)
type Cell struct {
r rune
attr CellAttributes
r rune
attr CellAttributes
image *image.RGBA
}
type CellAttributes struct {
@ -16,6 +23,59 @@ type CellAttributes struct {
Hidden bool
}
func (cell *Cell) Image() *image.RGBA {
return cell.image
}
func (cell *Cell) SetImage(img *image.RGBA) {
cell.image = img
}
func (cell *Cell) DrawImage(x, y float32) {
if cell.image == nil {
return
}
var tex uint32
gl.GenTextures(1, &tex)
gl.BindTexture(gl.TEXTURE_2D, tex)
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.TexImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
int32(cell.image.Bounds().Size().X),
int32(cell.image.Bounds().Size().Y),
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
gl.Ptr(cell.image.Pix),
)
gl.BindTexture(gl.TEXTURE_2D, 0)
var w float32 = float32(cell.image.Bounds().Size().X)
var h float32 = float32(cell.image.Bounds().Size().Y)
var readFboId uint32
gl.GenFramebuffers(1, &readFboId)
gl.BindFramebuffer(gl.READ_FRAMEBUFFER, readFboId)
gl.FramebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, tex, 0)
gl.BlitFramebuffer(0, 0, int32(w), int32(h),
int32(x), int32(y), int32(x+w), int32(y+h),
gl.COLOR_BUFFER_BIT, gl.LINEAR)
gl.BindFramebuffer(gl.READ_FRAMEBUFFER, 0)
gl.DeleteFramebuffers(1, &readFboId)
}
func (cell *Cell) Attr() CellAttributes {
return cell.attr
}

5
example.sixel Normal file
View File

@ -0,0 +1,5 @@
Pq
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
#1~~@@vv@@~~@@~~$
#2??}}GG}}??}}??-
#1!14@\

View File

@ -81,6 +81,8 @@ func (gui *GUI) resize(w *glfw.Window, width int, height int) {
gui.logger.Debugf("Setting viewport size...")
gl.Viewport(0, 0, int32(gui.width), int32(gui.height))
gui.terminal.SetCharSize(gui.renderer.cellWidth, gui.renderer.cellHeight)
gui.logger.Debugf("Resize complete!")
}
@ -191,6 +193,8 @@ func (gui *GUI) Render() error {
}
}()
gui.terminal.SetProgram(program)
for !gui.window.ShouldClose() {
select {
@ -242,10 +246,20 @@ func (gui *GUI) Render() error {
if hasText {
gui.renderer.DrawCellText(cell, uint(x), uint(y), nil)
}
if cell.Image() != nil {
ix := float32(x) * gui.renderer.cellWidth
iy := float32(gui.height) - (float32(y+1) * gui.renderer.cellHeight)
iy -= float32(cell.Image().Bounds().Size().Y)
gl.UseProgram(program)
cell.DrawImage(ix, iy)
}
}
}
gui.window.SwapBuffers()
}
}

View File

@ -214,6 +214,7 @@ func (r *OpenGLRenderer) DrawCellBg(cell buffer.Cell, col uint, row uint, cursor
rect.setColour(bg)
rect.Draw()
}
}
func (r *OpenGLRenderer) DrawCellText(cell buffer.Cell, col uint, row uint, colour *config.Colour) {

BIN
sixel/img.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

0
sixel/img.jpg Normal file
View File

240
sixel/sixel.go Normal file
View File

@ -0,0 +1,240 @@
package sixel
import (
"fmt"
"image"
"image/color"
"strconv"
"strings"
"github.com/go-gl/gl/v2.1/gl"
)
type Sixel struct {
px map[uint]map[uint]colour
width uint
height uint
}
type colour [3]uint8
func decompress(data string) string {
output := ""
inMarker := false
countStr := ""
for _, r := range data {
if !inMarker {
if r == '!' {
inMarker = true
countStr = ""
} else {
output = fmt.Sprintf("%s%c", output, r)
}
continue
}
if r >= 0x30 && r <= 0x39 {
countStr = fmt.Sprintf("%s%c", countStr, r)
} else {
count, _ := strconv.Atoi(countStr)
for i := 0; i < count; i++ {
output = fmt.Sprintf("%s%c", output, r)
}
inMarker = false
}
}
return output
}
// pass in everything after ESC+P and before ST
func ParseString(data string) (*Sixel, error) {
data = decompress(data)
inHeader := true
inColour := false
six := Sixel{}
var x, y uint
colourStr := ""
colourMap := map[string]colour{}
var selectedColour colour
headerStr := ""
remainMode := false
var ratio uint
// read p1 p2 p3
for i, r := range data {
switch true {
case inHeader:
// todo read p1 p2 p3
if r == 'q' {
headers := strings.Split(headerStr, ";")
switch headers[0] {
case "0", "1":
ratio = 5
case "2":
ratio = 3
case "", "3", "4", "5", "6":
ratio = 2
case "7", "8", "9":
ratio = 1
}
if len(headers) > 1 {
remainMode = headers[1] == "1"
}
inHeader = false
} else {
headerStr = fmt.Sprintf("%s%c", headerStr, r)
}
case inColour:
colourStr = fmt.Sprintf("%s%c", colourStr, r)
if i+1 >= len(data) || data[i+1] < 0x30 || data[i+1] > 0x3b {
// process colour string
inColour = false
parts := strings.Split(colourStr, ";")
// select colour
if len(parts) == 1 {
c, ok := colourMap[parts[0]]
if ok {
selectedColour = c
}
} else if len(parts) == 5 {
switch parts[1] {
case "1":
// HSL
return nil, fmt.Errorf("HSL colours are not yet supported")
case "2":
// RGB
r, _ := strconv.Atoi(parts[2])
g, _ := strconv.Atoi(parts[3])
b, _ := strconv.Atoi(parts[4])
colourMap[parts[0]] = colour([3]uint8{
uint8(r & 0xff),
uint8(g & 0xff),
uint8(b & 0xff),
})
default:
return nil, fmt.Errorf("Unknown colour definition type: %s", parts[1])
}
} else {
return nil, fmt.Errorf("Invalid colour directive: #%s", colourStr)
}
colourStr = ""
}
default:
switch r {
case '-':
y += 6
x = 0
case '$':
x = 0
case '#':
inColour = true
default:
if r < 63 || r > 126 {
continue
}
b := (r & 0xff) - 0x3f
var bit int
for bit = 5; bit >= 0; bit-- {
if b&(1<<uint(bit)) > 0 {
six.setPixel(x, y+uint(bit), selectedColour, ratio)
} else if !remainMode {
// @todo use background colour here
//six.setPixel(x, y+uint(bit), selectedColour)
}
}
x++
}
}
}
return &six, nil
}
func (six *Sixel) Draw() error {
rgba := six.RGBA()
var handle uint32
gl.GenTextures(1, &handle)
target := uint32(gl.TEXTURE_2D)
internalFmt := int32(gl.SRGB_ALPHA)
format := uint32(gl.RGBA)
width := int32(rgba.Rect.Size().X)
height := int32(rgba.Rect.Size().Y)
pixType := uint32(gl.UNSIGNED_BYTE)
dataPtr := gl.Ptr(rgba.Pix)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(target, handle)
// set the texture wrapping/filtering options (applies to current bound texture obj)
// TODO-cs
//gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_R, wrapR)
//gl.TexParameteri(texture.target, gl.TEXTURE_WRAP_S, wrapS)
gl.TexParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // minification filter
gl.TexParameteri(target, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // magnification filter
gl.TexImage2D(target, 0, internalFmt, width, height, 0, format, pixType, dataPtr)
// unbind
gl.BindTexture(target, 0)
return nil
}
func (six *Sixel) setPixel(x, y uint, c colour, vhRatio uint) {
if six.px == nil {
six.px = map[uint]map[uint]colour{}
}
if _, exists := six.px[x]; !exists {
six.px[x] = map[uint]colour{}
}
if x+1 > six.width {
six.width = x
}
ay := vhRatio * y
var i uint
for i = 0; i < vhRatio; i++ {
if ay+i+1 > six.height {
six.height = ay + i + 1
}
six.px[x][ay+i] = c
}
}
func (six *Sixel) RGBA() *image.RGBA {
rgba := image.NewRGBA(image.Rect(0, 0, int(six.width), int(six.height)))
for x, r := range six.px {
for y, colour := range r {
rgba.SetRGBA(int(x), int(six.height)-int(y), color.RGBA{
colour[0],
colour[1],
colour[2],
255,
})
}
}
return rgba
}

44
sixel/sixel_test.go Normal file
View File

@ -0,0 +1,44 @@
package sixel
import (
"bytes"
"io"
"log"
"os"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/image/bmp"
)
// from https://en.wikipedia.org/wiki/Sixel
func TestParsing(t *testing.T) {
//raw := `q"1;1;16;16#0;2;0;0;0#1;2;94;75;22#2;2;97;78;31#3;2;97;82;35#4;2;97;82;44#5;2;94;78;25#6;2;91;78;41#7;2;69;60;38#8;2;56;50;35#9;2;63;56;35#10;2;41;38;31#0NB@@!8?@@BN$#1oCA?@!6?@?ACo$#3?O??A?!4@?A??O$#4?_w{{!6}{{w_$#5?G#2CA?@!4?@?AC#5G-#1{_#6K!4?__!4?K#1_{$#5B#4FRrrrz^^zrrrRF#5B$#3?G_#7CCGC??CGCC#3_G$#2?O#9?G!8?G#2?O$#8!4?GC!4?CG-#0NKGG!8?GGKN$#1?BFE!8KEFB$#3???@!8?@$#4!4?@@!4?@@$#5!4?A!6?A$#2!5?A!4?A$#7!6?A??A$#10!6?!4@$#6!7?AA`
raw := `q
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
#1~~@@vv@@~~@@~~$
#2??}}GG}}??}}??-
#1!14@`
six, err := ParseString(raw)
require.Nil(t, err)
img := six.RGBA()
require.NotNil(t, img)
var imageBuf bytes.Buffer
err = bmp.Encode(io.Writer(&imageBuf), img)
if err != nil {
log.Panic(err)
}
// Write to file.
fo, err := os.Create("img.bmp")
if err != nil {
panic(err)
}
defer fo.Close()
fo.Write(imageBuf.Bytes())
}

View File

@ -12,6 +12,7 @@ var ansiSequenceMap = map[rune]escapeSequenceHandler{
'8': restoreCursorHandler,
'D': indexHandler,
'M': reverseIndexHandler,
'P': sixelHandler,
'c': risHandler, //RIS
'(': swallowHandler(1), // character set bullshit
')': swallowHandler(1), // character set bullshit

46
terminal/sixel.go Normal file
View File

@ -0,0 +1,46 @@
package terminal
import (
"fmt"
"math"
"github.com/go-gl/gl/all-core/gl"
"github.com/liamg/aminal/sixel"
)
func sixelHandler(pty chan rune, terminal *Terminal) error {
data := []rune{}
for {
b := <-pty
if b == 0x1b { // terminated by ESC bell or ESC \
_ = <-pty // swallow \ or bell
break
}
data = append(data, b)
}
six, err := sixel.ParseString(string(data))
if err != nil {
return fmt.Errorf("Failed to parse sixel data: %s", err)
}
x, y := terminal.ActiveBuffer().CursorColumn(), terminal.ActiveBuffer().CursorLine()
terminal.ActiveBuffer().Write(' ')
cell := terminal.ActiveBuffer().GetCell(x, y)
if cell == nil {
return fmt.Errorf("Missing cell for sixel")
}
gl.UseProgram(terminal.program)
cell.SetImage(six.RGBA())
imageHeight := float64(cell.Image().Bounds().Size().Y)
lines := int(math.Ceil(imageHeight / float64(terminal.charHeight)))
for l := 0; l <= int(lines+1); l++ {
terminal.ActiveBuffer().NewLine()
}
return nil
}

View File

@ -32,6 +32,7 @@ const (
)
type Terminal struct {
program uint32
buffers []*buffer.Buffer
activeBufferIndex uint8
lock sync.Mutex
@ -47,6 +48,8 @@ type Terminal struct {
mouseMode MouseMode
bracketedPasteMode bool
isDirty bool
charWidth float32
charHeight float32
}
type Modes struct {
@ -87,6 +90,11 @@ func New(pty *os.File, logger *zap.SugaredLogger, config *config.Config) *Termin
}
}
func (terminal *Terminal) SetProgram(program uint32) {
terminal.program = program
}
func (terminal *Terminal) SetBracketedPasteMode(enabled bool) {
terminal.bracketedPasteMode = enabled
}
@ -137,6 +145,11 @@ func (terminal *Terminal) ScrollDown(lines uint16) {
}
func (terminal *Terminal) SetCharSize(w float32, h float32) {
terminal.charWidth = w
terminal.charHeight = h
}
func (terminal *Terminal) ScrollUp(lines uint16) {
terminal.logger.Infof("Scrolling up %d", lines)
terminal.ActiveBuffer().ScrollUp(lines)