commit f40c9db
delthas
·
2025-09-26 10:04:09 +0000 UTC
parent 4173dac
Optimize loading previews of EXIF-rotated images
3 files changed,
+69,
-27
M
app.go
+2,
-1
1@@ -568,6 +568,7 @@ func (app *App) handleUIEvent(ev interface{}) bool {
2 // TODO: eat QuitEvent here?
3 switch ev := ev.(type) {
4 case vaxis.Resize:
5+ app.win.SetWinPixels(ev.XPixel, ev.YPixel)
6 app.win.Resize()
7 case vaxis.PasteStartEvent:
8 app.pasting = true
9@@ -1160,7 +1161,7 @@ func (app *App) fetchImage(link string) (image.Image, error) {
10 if err != nil {
11 return nil, err
12 }
13- img, _, err := ui.DecodeImage(res.Body)
14+ img, _, err := app.win.DecodeImage(res.Body)
15 res.Body.Close()
16 if err != nil {
17 return nil, err
+0,
-26
1@@ -1,12 +1,8 @@
2 package ui
3
4 import (
5- "bytes"
6 "encoding/binary"
7- "image"
8 "io"
9-
10- "github.com/disintegration/imaging"
11 )
12
13 func exifOrientation(r io.Reader) int {
14@@ -129,25 +125,3 @@ func exifOrientation(r io.Reader) int {
15 }
16 return 0 // Missing orientation tag.
17 }
18-
19-func DecodeImage(r io.Reader) (image.Image, string, error) {
20- var b bytes.Buffer
21- tr := io.TeeReader(io.LimitReader(r, 1<<20), &b)
22- o := exifOrientation(tr)
23- r = io.MultiReader(&b, r)
24-
25- img, format, err := image.Decode(r)
26- if err != nil {
27- return img, format, err
28- }
29-
30- switch o {
31- case 3:
32- img = imaging.Rotate180(img)
33- case 6:
34- img = imaging.Rotate270(img)
35- case 8:
36- img = imaging.Rotate90(img)
37- }
38- return img, format, nil
39-}
M
ui/ui.go
+67,
-0
1@@ -1,8 +1,10 @@
2 package ui
3
4 import (
5+ "bytes"
6 "fmt"
7 "image"
8+ "io"
9 "os"
10 "reflect"
11 "runtime"
12@@ -13,6 +15,7 @@ import (
13 "git.sr.ht/~rockorager/vaxis"
14 "git.sr.ht/~rockorager/vaxis/widgets/align"
15 "github.com/containerd/console"
16+ "github.com/disintegration/imaging"
17
18 "git.sr.ht/~delthas/senpai/events"
19 "git.sr.ht/~delthas/senpai/irc"
20@@ -45,6 +48,8 @@ type ConfigColors struct {
21 type Vaxis struct {
22 *vaxis.Vaxis
23 window vaxis.Window
24+ xPixel int
25+ yPixel int
26 }
27
28 type NotifyEvent struct {
29@@ -693,6 +698,11 @@ func (ui *UI) InputBackSearch() {
30 ui.e.BackSearch()
31 }
32
33+func (ui *UI) SetWinPixels(xPixel int, yPixel int) {
34+ ui.vx.xPixel = xPixel
35+ ui.vx.yPixel = yPixel
36+}
37+
38 func (ui *UI) Resize() {
39 ui.vx.window = ui.vx.Window() // Refresh window size
40 w, h := ui.vx.window.Size()
41@@ -1017,3 +1027,60 @@ func (ui *UI) drawVerticalMemberList(vx *Vaxis, x0, y0, width, height int, b *bu
42 printString(vx, &x, y, name)
43 }
44 }
45+
46+func (ui *UI) DecodeImage(r io.Reader) (image.Image, string, error) {
47+ var b bytes.Buffer
48+ tr := io.TeeReader(io.LimitReader(r, 1<<20), &b)
49+ o := exifOrientation(tr)
50+ r = io.MultiReader(&b, r)
51+
52+ img, format, err := image.Decode(r)
53+ if err != nil {
54+ return img, format, err
55+ }
56+
57+ w, h := ui.vx.window.Size()
58+ w = w * 9 / 10
59+ h = h * 9 / 10
60+ if w > 0 && h > 0 {
61+ wp := img.Bounds().Dx()
62+ hp := img.Bounds().Dy()
63+ switch o {
64+ case 6, 8:
65+ wp, hp = hp, wp
66+ }
67+ cellPixW := ui.vx.xPixel / w
68+ cellPixH := ui.vx.yPixel / h
69+ columns := (wp + cellPixW - 1) / cellPixW
70+ lines := (hp + cellPixH - 1) / cellPixH
71+ if columns > w || lines > h {
72+ sfX := float64(w) / float64(columns)
73+ sfY := float64(h) / float64(lines)
74+ nwp := wp
75+ nhp := hp
76+ switch {
77+ case sfX < sfY:
78+ nwp = int(sfX * float64(wp))
79+ nhp = int(sfX * float64(hp))
80+ case sfX > sfY:
81+ nwp = int(sfY * float64(wp))
82+ nhp = int(sfY * float64(hp))
83+ }
84+ switch o {
85+ case 6, 8:
86+ nwp, nhp = nhp, nwp
87+ }
88+ img = imaging.Resize(img, nwp, nhp, imaging.NearestNeighbor)
89+ }
90+ }
91+
92+ switch o {
93+ case 3:
94+ img = imaging.Rotate180(img)
95+ case 6:
96+ img = imaging.Rotate270(img)
97+ case 8:
98+ img = imaging.Rotate90(img)
99+ }
100+ return img, format, nil
101+}