From 3ac0a6b87c04374777d9405e3bddc1b29f337886 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Thu, 28 May 2026 16:28:19 -0700 Subject: Use the sixel package --- go.mod | 8 ++--- go.sum | 4 +-- icat.go | 109 +++++++++++++++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 89 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index 3ed9d4d..36cf1c7 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,10 @@ module roseh.moe/cmd/icat -go 1.24.0 - -toolchain go1.24.1 +go 1.26.3 require ( golang.org/x/image v0.32.0 - golang.org/x/term v0.36.0 + roseh.moe/pkg/sixel v0.0.0-20260528225709-a6d059daa153 ) -require golang.org/x/sys v0.37.0 // indirect +require golang.org/x/sys v0.37.0 diff --git a/go.sum b/go.sum index e9c590b..e7f3a84 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +roseh.moe/pkg/sixel v0.0.0-20260528225709-a6d059daa153 h1:Jq7Yv9BeWwBPA5BY238i0SpXDqKudsqvlTZUvbi6ZMg= +roseh.moe/pkg/sixel v0.0.0-20260528225709-a6d059daa153/go.mod h1:DX/c+l3VYm+yXoACY+HXl2gLV5o5iAG6gkHeDddTSrQ= diff --git a/icat.go b/icat.go index 8faf85f..a1922f1 100644 --- a/icat.go +++ b/icat.go @@ -2,7 +2,6 @@ package main import ( - "bufio" "errors" "flag" "fmt" @@ -16,14 +15,61 @@ import ( "golang.org/x/image/draw" _ "golang.org/x/image/tiff" _ "golang.org/x/image/webp" - "golang.org/x/term" + "golang.org/x/sys/unix" + "roseh.moe/pkg/sixel" ) var ( x = flag.Int("x", 0, "set image width in columns") y = flag.Int("y", 0, "set image height in rows") + m = flagPrintMode(flag.CommandLine, "m", modeSixel, "one of 'block', 'block24', or 'sixel'") ) +type printMode int + +const ( + modeInvalid printMode = iota + modeBlock + modeBlock24 + modeSixel +) + +type printModeValue printMode + +func (m *printModeValue) String() string { + switch printMode(*m) { + case modeBlock: + return "block" + case modeBlock24: + return "block24" + case modeSixel: + return "sixel" + default: + return "invalid" + } +} + +func (m *printModeValue) Set(s string) error { + var mode printMode + switch s { + case "block": + mode = modeBlock + case "block24": + mode = modeBlock24 + case "sixel": + mode = modeSixel + default: + return fmt.Errorf("bad mode type %q, should be one of 'block', 'block24', or 'sixel'", s) + } + *m = printModeValue(mode) + return nil +} + +func flagPrintMode(fs *flag.FlagSet, name string, value printMode, usage string) *printMode { + fs.Var((*printModeValue)(&value), name, usage) + return &value +} + func load(filename string) (image.Image, error) { file := os.Stdin if filename != "-" { @@ -43,30 +89,26 @@ func load(filename string) (image.Image, error) { return img, nil } -func printImg(img image.Image, cols, lines int) error { +func printImg(img image.Image, x, y, pixelX, pixelY int) error { // Try not to stretch the image. - if lines == 0 || cols != 0 && img.Bounds().Dy()*cols <= img.Bounds().Dx()*lines*5/2 { - lines = img.Bounds().Dy() * cols / (img.Bounds().Dx() * 5 / 2) + if y == 0 || x != 0 && img.Bounds().Dy()*x <= img.Bounds().Dx()*y*pixelY/pixelX { + y = img.Bounds().Dy() * x / (img.Bounds().Dx() * pixelY / pixelX) } else { - cols = img.Bounds().Dx() * lines * 5 / 2 / img.Bounds().Dy() + x = img.Bounds().Dx() * y * pixelY / pixelX / img.Bounds().Dy() } - dst := image.NewRGBA(image.Rect(0, 0, cols, 2*lines)) - draw.BiLinear.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Over, nil) - - w := bufio.NewWriter(os.Stdout) - defer w.Flush() - for y := 0; y < 2*lines; y += 2 { - for x := 0; x < cols; x++ { - hiR, hiG, hiB, _ := dst.At(x, y).RGBA() - loR, loG, loB, _ := dst.At(x, y+1).RGBA() - fmt.Fprintf(w, "\033[38;2;%d;%d;%dm\033[48;2;%d;%d;%dmâ–€", - hiR>>8, hiG>>8, hiB>>8, - loR>>8, loG>>8, loB>>8) - } - fmt.Fprintln(w, "\033[49m") + dst := image.NewRGBA(image.Rect(0, 0, x, y)) + draw.BiLinear.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Src, nil) + + switch *m { + case modeBlock: + sixel.PrintXTerm16(os.Stdout, dst) + case modeBlock24: + sixel.PrintBlock(os.Stdout, dst) + case modeSixel: + sixel.Print(os.Stdout, dst) } - fmt.Fprint(w, "\033[39m") + fmt.Println() return nil } @@ -81,13 +123,30 @@ func icat(args []string) error { cols := *x lines := *y + pixelX := 2 + pixelY := 5 if cols == 0 && lines == 0 { - var err error - cols, lines, err = term.GetSize(1) + ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ) if err != nil { - return fmt.Errorf("terminal size: %s", err) + return err } + cols = int(ws.Col) + lines = int(ws.Row) lines-- // Leave a line for the status bar. + cellX, cellY := int(ws.Xpixel)/int(ws.Col), int(ws.Ypixel)/int(ws.Row) + if *m == modeSixel { + cols *= cellX + lines *= cellY + } else { + pixelX, pixelY = cellX, cellY + } + } + switch *m { + case modeSixel: + pixelX, pixelY = 1, 1 + case modeBlock24: + lines *= 2 + pixelX *= 2 } img, err := load(file) @@ -95,7 +154,7 @@ func icat(args []string) error { return err } - if err := printImg(img, cols, lines); err != nil { + if err := printImg(img, cols, lines, pixelX, pixelY); err != nil { return err } return nil -- cgit v1.3.1