@@ -8,9 +8,12 @@ import (
88 "io"
99 "net/http"
1010 "net/url"
11+ "os"
12+ "strings"
1113 "time"
1214
1315 "github.com/charmbracelet/x/ansi/sixel"
16+ "github.com/dolmen-go/kittyimg"
1417 "golang.org/x/image/draw"
1518)
1619
@@ -20,45 +23,111 @@ func resizeImage(img image.Image, width, height int) image.Image {
2023 return dst
2124}
2225
23- func CardImage (imageURL string ) (string , error ) {
26+ // supportsKittyGraphics checks if the terminal supports the Kitty graphics protocol
27+ func supportsKittyGraphics () bool {
28+ // Check Kitty-specific window ID
29+ if os .Getenv ("KITTY_WINDOW_ID" ) != "" {
30+ return true
31+ }
32+
33+ // Check TERM_PROGRAM for known Kitty-compatible terminals
34+ termProgram := strings .ToLower (os .Getenv ("TERM_PROGRAM" ))
35+ switch termProgram {
36+ case "kitty" , "ghostty" , "wezterm" :
37+ return true
38+ }
39+
40+ // Check TERM variable for kitty or ghostty
41+ term := strings .ToLower (os .Getenv ("TERM" ))
42+ switch {
43+ case strings .Contains (term , "kitty" ):
44+ return true
45+ case strings .Contains (term , "ghostty" ):
46+ return true
47+ }
48+
49+ return false
50+ }
51+
52+ // supportsSixelGraphics checks if the terminal supports the Sixel graphics protocol
53+ func supportsSixelGraphics () bool {
54+ session := os .Getenv ("WT_SESSION" )
55+ termProgram := strings .ToLower (os .Getenv ("TERM_PROGRAM" ))
56+ term := strings .ToLower (os .Getenv ("TERM" ))
57+
58+ if session != "" {
59+ return true
60+ }
61+
62+ // Check TERM_PROGRAM for known Sixel-supporting terminals
63+ switch termProgram {
64+ case "iterm.app" , "wezterm" , "konsole" , "tabby" , "rio" :
65+ return true
66+ }
67+
68+ // Check TERM variable for known Sixel-supporting terminals
69+ switch {
70+ case term == "foot" || strings .HasPrefix (term , "foot-" ):
71+ return true
72+ case term == "xterm-sixel" || strings .Contains (term , "sixel" ):
73+ return true
74+ }
75+
76+ return false
77+ }
78+
79+ // CardImage downloads and renders an image using Kitty protocol if supported, otherwise Sixel.
80+ func CardImage (imageURL string ) (imageData string , protocol string , err error ) {
2481 client := & http.Client {
2582 Timeout : time .Second * 60 ,
2683 }
2784 parsedURL , err := url .Parse (imageURL )
2885 if err != nil || (parsedURL .Scheme != "http" && parsedURL .Scheme != "https" ) {
29- return "" , errors .New ("invalid URL scheme" )
86+ return "" , "" , errors .New ("invalid URL scheme" )
3087 }
3188 resp , err := client .Get (imageURL )
3289 if err != nil {
33- return "" , fmt .Errorf ("failed to fetch image: %w" , err )
90+ return "" , "" , fmt .Errorf ("failed to fetch image: %w" , err )
3491 }
3592 defer resp .Body .Close ()
3693
3794 if resp .StatusCode != http .StatusOK {
38- return "" , fmt .Errorf ("non-200 response: %d" , resp .StatusCode )
95+ return "" , "" , fmt .Errorf ("non-200 response: %d" , resp .StatusCode )
3996 }
4097
4198 // Read body into memory first to avoid timeout during decode
4299 limitedBody := io .LimitReader (resp .Body , 10 * 1024 * 1024 )
43100 bodyBytes , err := io .ReadAll (limitedBody )
44101 if err != nil {
45- return "" , fmt .Errorf ("failed to read image data: %w" , err )
102+ return "" , "" , fmt .Errorf ("failed to read image data: %w" , err )
46103 }
47104
48105 img , _ , err := image .Decode (bytes .NewReader (bodyBytes ))
49106 if err != nil {
50- return "" , fmt .Errorf ("failed to decode image: %w" , err )
107+ return "" , "" , fmt .Errorf ("failed to decode image: %w" , err )
51108 }
52109
53110 resized := resizeImage (img , 500 , 675 )
54111
55- // Build Sixel string to return
56112 var buf bytes.Buffer
57- buf .WriteString ("\x1b Pq" )
58- if err := new (sixel.Encoder ).Encode (& buf , resized ); err != nil {
59- return "" , fmt .Errorf ("failed to encode sixel: %w" , err )
113+
114+ if supportsKittyGraphics () {
115+ if err := kittyimg .Fprint (& buf , resized ); err != nil {
116+ return "" , "" , fmt .Errorf ("failed to encode kitty image: %w" , err )
117+ }
118+ return buf .String (), "Kitty" , nil
119+ }
120+
121+ // Fall back to Sixel
122+ if supportsSixelGraphics () {
123+ buf .WriteString ("\x1b Pq" )
124+ if err := new (sixel.Encoder ).Encode (& buf , resized ); err != nil {
125+ return "" , "" , fmt .Errorf ("failed to encode sixel: %w" , err )
126+ }
127+ buf .WriteString ("\x1b \\ " )
128+ return buf .String (), "Sixel" , nil
60129 }
61- buf .WriteString ("\x1b \\ " )
62130
63- return buf .String (), nil
131+ // Neither protocol is supported
132+ return "" , "" , errors .New ("your terminal does not support image rendering (Kitty or Sixel graphics protocols required)\n \n Try using: Kitty, Ghostty, WezTerm, iTerm2, or foot" )
64133}
0 commit comments