Skip to content

Commit bd27700

Browse files
committed
feat: Add support for scaling images up/down for function, embed different preview image sizes in RSS feed
Signed-off-by: Felicitas Pojtinger <felicitas@pojtinger.com>
1 parent f9a7c42 commit bd27700

7 files changed

Lines changed: 86 additions & 28 deletions

File tree

assets/js/event-detail-modal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ customElements.define(
109109

110110
const cover = $("cover");
111111
if (data.cover_url) {
112-
cover.src = proxyImageUrl(data.cover_url, api);
112+
cover.src = proxyImageUrl(data.cover_url, api, "lg");
113113
cover.alt = data.name;
114114
} else {
115115
cover.remove();

assets/js/events.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
export const proxyImageUrl = (url, api) => {
1+
export const proxyImageUrl = (url, api, size = "lg") => {
22
if (!url) return "";
3-
return `${api}/image?url=${encodeURIComponent(url)}`;
3+
return `${api}/image?url=${encodeURIComponent(url)}&size=${size}`;
44
};
55

66
export const formatEvent = (evt, api) => {
@@ -32,7 +32,7 @@ export const formatEvent = (evt, api) => {
3232
? `https://luma.com/${encodeURI(evt.url)}`
3333
: "https://luma.com/vanlug",
3434
location: evt.location || "",
35-
coverUrl: proxyImageUrl(evt.cover_url, api),
35+
coverUrl: proxyImageUrl(evt.cover_url, api, "sm"),
3636
};
3737
};
3838

assets/js/mastodon-feed.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const renderToot = (toot, data, { isFirst, isLast, inDrawer, profile, api }) =>
3838
const mediaSlot = $("media");
3939
for (const m of toot.media?.slice(0, 1) ?? []) {
4040
const el = document.createElement(m.isVideo ? "video" : "img");
41-
el.src = proxyImageUrl(m.url, api);
41+
el.src = proxyImageUrl(m.url, api, "md");
4242
if (m.isVideo) el.controls = true;
4343
else el.alt = m.altText || "";
4444
el.className = "pf-v6-u-w-100";
@@ -50,7 +50,7 @@ const renderToot = (toot, data, { isFirst, isLast, inDrawer, profile, api }) =>
5050
if (!mediaSlot.children.length) mediaSlot.remove();
5151

5252
const avatar = $("avatar");
53-
avatar.src = proxyImageUrl(data.userProfilePictureURL, api);
53+
avatar.src = proxyImageUrl(data.userProfilePictureURL, api, "sm");
5454
avatar.alt = data.userDisplayName;
5555

5656
$("timestamp").textContent = new Date(toot.timestamp).toLocaleDateString(

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
module github.com/vanlug/vanlug.github.io
22

3-
go 1.25.0
3+
go 1.26
44

55
require (
66
github.com/mattn/go-mastodon v0.0.11 // indirect
77
github.com/pojntfx/felicitas.pojtinger.com v0.4.1
88
)
99

1010
require (
11+
github.com/anthonynsimon/bild v0.15.0
1112
github.com/gorilla/feeds v1.2.0
1213
github.com/nicksrandall/prosemirror-go v0.0.0-20170601171447-b0778c4954c7
14+
golang.org/x/image v0.38.0
1315
golang.org/x/sync v0.20.0
1416
)
1517

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/anthonynsimon/bild v0.15.0 h1:FzvaNLuNlAPKw1Xz7V2WYOcGIEBMj8Y6ZyAk7CI+HzA=
2+
github.com/anthonynsimon/bild v0.15.0/go.mod h1:qIgJ9FldkCn0iy5Ad24fzUkz5R+iJ0WfhiV+6FeCB5A=
13
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
24
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
@@ -22,6 +24,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
2224
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
2325
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=
2426
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=
27+
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
28+
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
2529
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
2630
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
2731
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

pkg/handlers/images.go

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,46 @@
11
package handlers
22

33
import (
4+
"image"
5+
_ "image/jpeg"
6+
_ "image/png"
47
"io"
58
"net/http"
69
"net/url"
10+
11+
"github.com/anthonynsimon/bild/imgio"
12+
"github.com/anthonynsimon/bild/transform"
13+
_ "golang.org/x/image/webp"
714
)
815

916
var allowedImageHosts = map[string]bool{
1017
"images.lumacdn.com": true,
1118
"s3.us-west-000.backblazeb2.com": true,
1219
}
1320

21+
var sizeWidths = map[string]int{
22+
"sm": 192,
23+
"md": 512,
24+
"lg": 1024,
25+
}
26+
1427
func ImageProxyHandler(w http.ResponseWriter, r *http.Request) {
1528
upstream := r.URL.Query().Get("url")
1629
if upstream == "" {
1730
http.Error(w, "missing url query parameter", http.StatusBadRequest)
1831
return
1932
}
2033

34+
size := r.URL.Query().Get("size")
35+
if size == "" {
36+
size = "lg"
37+
}
38+
maxWidth, ok := sizeWidths[size]
39+
if !ok {
40+
http.Error(w, "invalid size parameter, must be sm, md, or lg", http.StatusBadRequest)
41+
return
42+
}
43+
2144
parsed, err := url.Parse(upstream)
2245
if err != nil || parsed.Scheme != "https" {
2346
http.Error(w, "invalid url", http.StatusBadRequest)
@@ -41,13 +64,40 @@ func ImageProxyHandler(w http.ResponseWriter, r *http.Request) {
4164
return
4265
}
4366

44-
if ct := resp.Header.Get("Content-Type"); ct != "" {
45-
w.Header().Set("Content-Type", ct)
46-
}
67+
ct := resp.Header.Get("Content-Type")
4768
w.Header().Set("Cache-Control", "public, max-age=86400")
48-
if cl := resp.Header.Get("Content-Length"); cl != "" {
49-
w.Header().Set("Content-Length", cl)
50-
}
5169

52-
io.Copy(w, resp.Body)
70+
switch ct {
71+
case "image/jpeg", "image/png", "image/webp":
72+
src, format, err := image.Decode(resp.Body)
73+
if err != nil {
74+
http.Error(w, "failed to decode image", http.StatusBadGateway)
75+
return
76+
}
77+
78+
srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()
79+
dstW, dstH := srcW, srcH
80+
if srcW > maxWidth {
81+
dstW = maxWidth
82+
dstH = srcH * maxWidth / srcW
83+
}
84+
85+
dst := transform.Resize(src, dstW, dstH, transform.Lanczos)
86+
87+
encoder := imgio.JPEGEncoder(80)
88+
outFormat := "jpeg"
89+
if format == "png" {
90+
encoder = imgio.PNGEncoder()
91+
outFormat = "png"
92+
}
93+
94+
w.Header().Set("Content-Type", "image/"+outFormat)
95+
encoder(w, dst)
96+
97+
default:
98+
if ct != "" {
99+
w.Header().Set("Content-Type", ct)
100+
}
101+
io.Copy(w, resp.Body)
102+
}
53103
}

pkg/handlers/luma.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@ var feedEntryTmpl = template.Must(
3030
Parse(feedEntryTmplStr))
3131

3232
type feedEntryData struct {
33-
CoverURL string `json:"cover_url,omitempty"`
34-
Name string `json:"name"`
35-
DateLine string `json:"date_line"`
36-
TimeLine string `json:"time_line"`
37-
Location string `json:"location,omitempty"`
38-
FullAddress string `json:"full_address,omitempty"`
39-
Hosts string `json:"hosts,omitempty"`
40-
Admission string `json:"admission,omitempty"`
41-
Description string `json:"description,omitempty"`
42-
Link string `json:"link"`
43-
MapURL string `json:"map_url,omitempty"`
33+
CoverURL string `json:"cover_url,omitempty"`
34+
CoverURLPreview string `json:"-"`
35+
Name string `json:"name"`
36+
DateLine string `json:"date_line"`
37+
TimeLine string `json:"time_line"`
38+
Location string `json:"location,omitempty"`
39+
FullAddress string `json:"full_address,omitempty"`
40+
Hosts string `json:"hosts,omitempty"`
41+
Admission string `json:"admission,omitempty"`
42+
Description string `json:"description,omitempty"`
43+
Link string `json:"link"`
44+
MapURL string `json:"map_url,omitempty"`
4445
}
4546

4647
func caTime(s string) string {
@@ -494,7 +495,8 @@ func EventsFeedHandler(w http.ResponseWriter, r *http.Request, apiBase string, e
494495
data := buildEntryData(item, descriptions[i], lumaBase, mapBase)
495496

496497
if data.CoverURL != "" {
497-
data.CoverURL = apiURL + "/image?" + url.Values{"url": {data.CoverURL}}.Encode()
498+
data.CoverURLPreview = apiURL + "/image?" + url.Values{"url": {data.CoverURL}, "size": {"md"}}.Encode()
499+
data.CoverURL = apiURL + "/image?" + url.Values{"url": {data.CoverURL}, "size": {"lg"}}.Encode()
498500
}
499501

500502
published := now
@@ -522,9 +524,9 @@ func EventsFeedHandler(w http.ResponseWriter, r *http.Request, apiBase string, e
522524
Content: buf.String(),
523525
}
524526

525-
if data.CoverURL != "" {
527+
if data.CoverURLPreview != "" {
526528
feedItem.Enclosure = &feeds.Enclosure{
527-
Url: data.CoverURL,
529+
Url: data.CoverURLPreview,
528530
}
529531
}
530532

0 commit comments

Comments
 (0)