-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimage.go
More file actions
156 lines (124 loc) · 3.42 KB
/
Copy pathimage.go
File metadata and controls
156 lines (124 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// if performance is bad, consider wezterm imgcat <file> as a last resort
// https://github.com/gdamore/tcell/blob/88b9c25c3c5ee48b611dfeca9a2e9cf07812c35e/_demos/sixel.go#L39
// https://github.com/otiai10/gat/blob/228567838a2b2db10cc55d0e9bf74707967fa671/render/sixel.go
package main
import (
"fmt"
"image"
"image/jpeg"
"image/png"
"log"
"os"
"path/filepath"
"strings"
"github.com/dolmen-go/kittyimg" // faster than mattn/go-sixel
"github.com/nfnt/resize"
)
type Size struct {
width int // in chars
height int // in chars
}
// these values are unintuitive (since characters in a terminal are usually
// taller than they are wide), but they were derived from fiddling with the
// x/y-padding formulae
const (
CharHeightPx = 9
CharWidthPx = 15
)
func decode(fname string) (img image.Image, ierr error) {
fo, err := os.Open(fname)
if err != nil {
// panic(err)
return nil, err
}
defer fo.Close()
// var ierr error
switch filepath.Ext(fname) {
case ".png":
img, ierr = png.Decode(fo)
case ".jpg":
img, ierr = jpeg.Decode(fo)
// TODO: handle webm thumbnail?
default:
fmt.Println(fname)
log.Println("failed to decode", fname)
return
}
// if ierr != nil {
// // panic(err)
// return nil
// }
return img, ierr
}
// Render a single image to stdout. Constraining the image to a given size is
// recommended, as rendering time scales quadratically with image size.
func Render(fname string, size *Size) {
img, err := decode(fname)
if err != nil {
return
}
if img == nil {
return
}
// // img size in pixels
// imgX := img.Bounds().Max.X
// imgY := img.Bounds().Max.Y
if size != nil {
// https://en.wikipedia.org/wiki/Comparison_gallery_of_image_scaling_algorithms
interp := resize.Lanczos2 // Bilinear, Bicubic
// always resize by height, preserving aspect ratio
// note that size refers to the entire screen, so we /2 to
// account for upper pane. this should eventually be the
// responsibility of caller
img = resize.Resize(
/* w */ 0,
/* h */ CharHeightPx*96, // 96 is magic, apparently
img,
interp,
)
imgX := img.Bounds().Max.X
imgY := img.Bounds().Max.Y
xPad := (size.width - (imgX / CharWidthPx)) / 2
// log.Println("yPad", yPad)
if xPad >= 0 {
// we don't need y padding for (portrait) images;
// resizing to an appropriate height is usually good
// enough
} else {
img = resize.Resize(
/* w */ CharWidthPx*110,
/* h */ 0,
img,
interp,
)
newImgX := img.Bounds().Max.X
newImgY := img.Bounds().Max.Y
// ultra wide image
log.Println(
"image too large! initial dims:",
imgX,
imgY,
"resized:",
newImgX,
newImgY,
)
yPad := (size.height - (newImgY / CharHeightPx)) / 2
fmt.Println(strings.Repeat("\n", max(0, yPad)))
xPad = (size.width - (newImgX / CharWidthPx)) / 2
}
fmt.Println(strings.Repeat(" ", xPad))
}
// if err := sixel.NewEncoder(os.Stdout).Encode(img); err != nil {
// panic(err)
// }
if err := kittyimg.Fprintln(os.Stdout, img); err != nil {
panic(err)
}
// the result is a b64-encoded string; s and v refer to width and
// height, for example. the protocol spec is 'loosely' documented
// within kitty, but more concrete examples can be gleaned from related
// repos.
//
// https://sw.kovidgoyal.net/kitty/graphics-protocol/#the-graphics-escape-code
// https://github.com/benjajaja/ratatui-image/blob/afbdd4e79251ef0709e4a2d9281b3ac6eb73291a/src/protocol/kitty.rs#L150
}