aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--go.mod8
-rw-r--r--go.sum4
-rw-r--r--icat.go107
3 files changed, 88 insertions, 31 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)
+ dst := image.NewRGBA(image.Rect(0, 0, x, y))
+ draw.BiLinear.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Src, 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")
+ 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