bannergen/main.go

200 lines
5.2 KiB
Go
Raw Normal View History

2025-01-13 19:21:10 -06:00
package main
2025-01-13 22:50:12 -06:00
import (
"context"
"embed"
"flag"
"fmt"
"image"
"image/color"
"image/png"
"io"
"math"
"net/http"
"runtime/debug"
"text/template"
"time"
"golang.org/x/image/draw"
"git.chandlerswift.com/chandlerswift/bannergen/internal/datafetchers"
"git.chandlerswift.com/chandlerswift/bannergen/internal/datafetchers/espn"
"git.chandlerswift.com/chandlerswift/bannergen/internal/types"
)
//go:embed static templates
var frontend embed.FS
2025-01-13 19:21:10 -06:00
func main() {
2025-01-13 22:50:12 -06:00
port := flag.Int("port", 8000, "Port to listen on")
flag.Parse()
// preload teams data
fetchers := []datafetchers.DataFetcher{
espn.ESPNDataFetcher{},
}
var sports []types.Sport
for _, fetcher := range fetchers {
sports = append(sports, fetcher.Fetch()...)
}
rawBuildInfo, _ := debug.ReadBuildInfo()
var revision, time string
var dirty bool
for _, buildSetting := range rawBuildInfo.Settings {
if buildSetting.Key == "vcs.revision" {
revision = buildSetting.Value
} else if buildSetting.Key == "vcs.time" {
time = buildSetting.Value
} else if buildSetting.Key == "vcs.modified" {
dirty = buildSetting.Value == "true"
}
}
buildInfo := map[string]interface{}{
"GitRev": revision,
"GitDirty": dirty,
"GitTime": time,
"GoVersion": rawBuildInfo.GoVersion,
"AllBuildInfo": rawBuildInfo.String(),
}
http.Handle("GET /static/", http.FileServer(http.FS(frontend)))
tmpls := template.Must(template.ParseFS(frontend, "templates/*"))
http.HandleFunc("GET /{$}", func(w http.ResponseWriter, r *http.Request) {
err := tmpls.ExecuteTemplate(w, "index.html", map[string]interface{}{})
if err != nil {
panic(err)
}
})
http.HandleFunc("GET /about.html", func(w http.ResponseWriter, r *http.Request) {
err := tmpls.ExecuteTemplate(w, "about.html", map[string]interface{}{
"buildInfo": buildInfo,
})
if err != nil {
panic(err)
}
})
http.HandleFunc("/generate/{image_name}", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
colorLeftHex := r.Form.Get("left_color")
if colorLeftHex == "" {
http.Error(w, "no left_color provided", 400)
}
colorRightHex := r.Form.Get("right_color")
if colorRightHex == "" {
http.Error(w, "no right_color provided", 400)
}
logoLeftPath := r.Form.Get("left_logo")
if logoLeftPath == "" {
http.Error(w, "no left_logo provided", 400)
}
logoRightPath := r.Form.Get("right_logo")
if logoRightPath == "" {
http.Error(w, "no right_logo provided", 400)
}
size := 400 // TODO: param
// Set image dimensions; TODO: param
const width, height = 1280, 720
// Create a blank RGBA image
img := image.NewRGBA(image.Rect(0, 0, width, height))
// Convert hex colors to RGBA
colorLeft := hexToRGBA(colorLeftHex)
colorRight := hexToRGBA(colorRightHex)
// Fill the image with colors split by angle
centerX, centerY := float64(width)/2, float64(height)/2
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
dx, dy := float64(x)-centerX, float64(y)-centerY
rotatedAngle := math.Atan2(dy, dx) - 16.0*math.Pi/180
if math.Cos(rotatedAngle) >= 0 {
img.Set(x, y, colorRight)
} else {
img.Set(x, y, colorLeft)
}
}
}
// Load logos
logoLeft, err := loadPNG(logoLeftPath)
if err != nil {
panic(err)
}
logoRight, err := loadPNG(logoRightPath)
if err != nil {
panic(err)
}
// Resize logos using golang.org/x/image/draw
logoLeftResized := resizeImage(logoLeft, size, size)
logoRightResized := resizeImage(logoRight, size, size)
// Place logos
logoLeftX, logoLeftY := (width/4)-(size/2), (height/2)-(size/2)
logoRightX, logoRightY := (3*width/4)-(size/2), (height/2)-(size/2)
draw.Draw(img, image.Rect(logoLeftX, logoLeftY, logoLeftX+size, logoLeftY+size), logoLeftResized, image.Point{}, draw.Over)
draw.Draw(img, image.Rect(logoRightX, logoRightY, logoRightX+size, logoRightY+size), logoRightResized, image.Point{}, draw.Over)
png.Encode(w, img)
})
panic(http.ListenAndServe(fmt.Sprintf(":%v", *port), nil))
}
func resizeImage(src *image.RGBA, newWidth, newHeight int) *image.RGBA {
dst := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
return dst
}
func hexToRGBA(hex string) color.RGBA {
var r, g, b int
var err error
if hex[0] == '#' {
_, err = fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
} else {
_, err = fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b)
}
if err != nil {
panic(err)
}
return color.RGBA{uint8(r), uint8(g), uint8(b), 255}
}
func loadPNG(path string) (*image.RGBA, error) {
var reader io.ReadCloser
var err error
// TODO: smarter limits here
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", path, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("failed to fetch image: %s", resp.Status)
}
reader = resp.Body
defer reader.Close()
img, err := png.Decode(reader)
if err != nil {
return nil, err
}
2025-01-13 19:21:10 -06:00
2025-01-13 22:50:12 -06:00
// Convert to RGBA
rgbaImg := image.NewRGBA(img.Bounds())
draw.Draw(rgbaImg, rgbaImg.Bounds(), img, image.Point{}, draw.Src)
return rgbaImg, nil
2025-01-13 19:21:10 -06:00
}