mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-09 15:38:17 -07:00
feat: Rewind (#116)
* wip * chore: update counts to allow unix timeframe * feat: add db functions for counting new items * wip: endpoint working * wip * wip: initial ui done * add header, adjust ui * add time listened toggle * fix layout, year param * param fixes
This commit is contained in:
parent
c0a8c64243
commit
d4ac96f780
64 changed files with 2252 additions and 1055 deletions
186
internal/summary/image.go
Normal file
186
internal/summary/image.go
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package summary
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
_ "image/jpeg"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
var (
|
||||
assetPath = path.Join("..", "..", "assets")
|
||||
titleFontPath = path.Join(assetPath, "LeagueSpartan-Medium.ttf")
|
||||
textFontPath = path.Join(assetPath, "Jost-Regular.ttf")
|
||||
paddingLg = 30
|
||||
paddingMd = 20
|
||||
paddingSm = 6
|
||||
featuredImageSize = 180
|
||||
titleFontSize = 48.0
|
||||
textFontSize = 16.0
|
||||
featureTextStart = paddingLg + paddingMd + featuredImageSize
|
||||
)
|
||||
|
||||
// lots of code borrowed from https://medium.com/@daniel.ruizcamacho/how-to-create-an-image-in-golang-step-by-step-4416affe088f
|
||||
// func GenerateImage(summary *Summary) error {
|
||||
// base := image.NewRGBA(image.Rect(0, 0, 750, 1100))
|
||||
// draw.Draw(base, base.Bounds(), image.NewUniform(color.Black), image.Pt(0, 0), draw.Over)
|
||||
|
||||
// file, err := os.Create(path.Join(cfg.ConfigDir(), "summary.png"))
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// defer file.Close()
|
||||
|
||||
// // add title
|
||||
// if err := addText(base, summary.Title, "", image.Pt(paddingLg, 60), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// // add images
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// if err := addImage(base, summary.TopArtistImage, image.Pt(-paddingLg, -120-(featuredImageSize+paddingLg)*2), featuredImageSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// // top artists text
|
||||
// if err := addText(base, "Top Artists", "", image.Pt(featureTextStart, 132), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// for rank, artist := range summary.TopArtists {
|
||||
// if rank == 0 {
|
||||
// if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// } else {
|
||||
// if err := addText(base, artist.Name, strconv.Itoa(artist.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // top albums text
|
||||
// if err := addText(base, "Top Albums", "", image.Pt(featureTextStart, 132+featuredImageSize+paddingLg), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// for rank, album := range summary.TopAlbums {
|
||||
// if rank == 0 {
|
||||
// if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, featuredImageSize+10), titleFontPath, titleFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// } else {
|
||||
// if err := addText(base, album.Title, strconv.Itoa(album.Plays)+" plays", image.Pt(featureTextStart, 210+(rank*(int(textFontSize)+paddingSm))), textFontPath, textFontSize); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: %w", err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // top tracks text
|
||||
|
||||
// // stats text
|
||||
|
||||
// if err := png.Encode(file, base); err != nil {
|
||||
// return fmt.Errorf("GenerateImage: png.Encode: %w", err)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func addImage(baseImage *image.RGBA, path string, point image.Point, height int) error {
|
||||
templateFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
template, _, err := image.Decode(templateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resized := resize(template, height, height)
|
||||
|
||||
draw.Draw(baseImage, baseImage.Bounds(), resized, point, draw.Over)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addText(baseImage *image.RGBA, text, subtext string, point image.Point, fontFile string, fontSize float64) error {
|
||||
fontBytes, err := os.ReadFile(fontFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ttf, err := opentype.Parse(fontBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
face, err := opentype.NewFace(ttf, &opentype.FaceOptions{
|
||||
Size: fontSize,
|
||||
DPI: 72,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
drawer := &font.Drawer{
|
||||
Dst: baseImage,
|
||||
Src: image.NewUniform(color.White),
|
||||
Face: face,
|
||||
Dot: fixed.Point26_6{
|
||||
X: fixed.I(point.X),
|
||||
Y: fixed.I(point.Y),
|
||||
},
|
||||
}
|
||||
|
||||
drawer.DrawString(text)
|
||||
if subtext != "" {
|
||||
face, err = opentype.NewFace(ttf, &opentype.FaceOptions{
|
||||
Size: textFontSize,
|
||||
DPI: 72,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
drawer.Face = face
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
drawer.Src = image.NewUniform(color.RGBA{200, 200, 200, 255})
|
||||
drawer.DrawString(" - ")
|
||||
drawer.DrawString(subtext)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resize(m image.Image, w, h int) *image.RGBA {
|
||||
if w < 0 || h < 0 {
|
||||
return nil
|
||||
}
|
||||
r := m.Bounds()
|
||||
if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
|
||||
return image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
}
|
||||
curw, curh := r.Dx(), r.Dy()
|
||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
for y := range h {
|
||||
for x := range w {
|
||||
// Get a source pixel.
|
||||
subx := x * curw / w
|
||||
suby := y * curh / h
|
||||
r32, g32, b32, a32 := m.At(subx, suby).RGBA()
|
||||
r := uint8(r32 >> 8)
|
||||
g := uint8(g32 >> 8)
|
||||
b := uint8(b32 >> 8)
|
||||
a := uint8(a32 >> 8)
|
||||
img.SetRGBA(x, y, color.RGBA{r, g, b, a})
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue