174 lines
4.8 KiB
Go
174 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gen2brain/avif"
|
|
"golang.org/x/image/draw"
|
|
"golang.org/x/sync/errgroup"
|
|
"zombiezen.com/go/sqlite"
|
|
"zombiezen.com/go/sqlite/sqlitex"
|
|
)
|
|
|
|
func saveImgs(ctx context.Context, db *sqlitex.Pool, out string) {
|
|
skill, err := skillIconIDs(ctx, db)
|
|
if err != nil {
|
|
slog.Error("skills", slog.Any("err", err))
|
|
}
|
|
chara, err := charaIDs(ctx, db)
|
|
if err != nil {
|
|
slog.Error("characters", slog.Any("err", err))
|
|
}
|
|
rank, err := rankIDs(ctx, db)
|
|
if err != nil {
|
|
slog.Error("ranks", slog.Any("err", err))
|
|
}
|
|
|
|
g, ctx := errgroup.WithContext(ctx)
|
|
tick := time.NewTicker(250 * time.Millisecond)
|
|
idouts(ctx, g, tick, skill, filepath.Join(out, "skill"), "https://media.gametora.com/umamusume/skills/icon/%d.png", image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
|
|
idouts(ctx, g, tick, chara, filepath.Join(out, "chara"), "https://gametora.com/images/umamusume/characters/icons/chr_icon_%d.png", image.Pt(256, 256), image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
|
|
idouts(ctx, g, tick, rank, filepath.Join(out, "rank"), "https://media.gametora.com/umamusume/ui/rank/%02d.png", image.Pt(128, 128), image.Pt(64, 64), image.Pt(32, 32), image.Pt(16, 16))
|
|
idouts(ctx, g, tick, rank, filepath.Join(out, "rank", "simple"), "https://media.gametora.com/umamusume/ui/rank/simple/%02d.png", image.Pt(50, 50), image.Pt(32, 32), image.Pt(16, 16))
|
|
if err := g.Wait(); err != nil {
|
|
slog.Error("output", slog.Any("err", err))
|
|
}
|
|
}
|
|
|
|
// load scans all results of sql and appends them to r.
|
|
func load[T any](ctx context.Context, db *sqlitex.Pool, r []T, sql string, row func(*sqlite.Stmt) T) ([]T, error) {
|
|
conn, err := db.Take(ctx)
|
|
defer db.Put(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stmt, err := conn.Prepare(sql)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for {
|
|
ok, err := stmt.Step()
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
if !ok {
|
|
break
|
|
}
|
|
r = append(r, row(stmt))
|
|
}
|
|
return r, err
|
|
}
|
|
|
|
func skillIconIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
|
|
return load(ctx, db, nil, "SELECT DISTINCT icon_id FROM skill_data", func(s *sqlite.Stmt) int32 {
|
|
return s.ColumnInt32(0)
|
|
})
|
|
}
|
|
|
|
func charaIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
|
|
return load(ctx, db, nil, "SELECT id FROM chara_data", func(s *sqlite.Stmt) int32 {
|
|
return s.ColumnInt32(0)
|
|
})
|
|
}
|
|
|
|
func rankIDs(ctx context.Context, db *sqlitex.Pool) ([]int32, error) {
|
|
return load(ctx, db, nil, "SELECT id-1 FROM single_mode_rank", func(s *sqlite.Stmt) int32 {
|
|
return s.ColumnInt32(0)
|
|
})
|
|
}
|
|
|
|
var pngenc = &png.Encoder{CompressionLevel: png.BestCompression}
|
|
|
|
func idouts(ctx context.Context, g *errgroup.Group, tick *time.Ticker, ids []int32, p, urlf string, sizes ...image.Point) {
|
|
for _, id := range ids {
|
|
g.Go(func() error {
|
|
n := strconv.Itoa(int(id))
|
|
p := filepath.Join(p, n)
|
|
_, err := os.Stat(p)
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
slog.DebugContext(ctx, "exists", slog.String("path", p))
|
|
return nil
|
|
}
|
|
if err := os.MkdirAll(p, 0777); err != nil {
|
|
return err
|
|
}
|
|
url := fmt.Sprintf(urlf, id)
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-tick.C: // continue on
|
|
}
|
|
img, err := dlimg(ctx, url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
slog.InfoContext(ctx, "resize", slog.String("url", url))
|
|
rs := resize(img, sizes...)
|
|
for _, r := range rs {
|
|
g.Go(func() error {
|
|
m := r.Bounds().Max
|
|
o := filepath.Join(p, fmt.Sprintf("%dx%d.png", m.X, m.Y))
|
|
slog.InfoContext(ctx, "write", slog.String("path", o))
|
|
f, err := os.Create(o)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
return pngenc.Encode(f, r)
|
|
})
|
|
g.Go(func() error {
|
|
m := r.Bounds().Max
|
|
o := filepath.Join(p, fmt.Sprintf("%dx%d.avif", m.X, m.Y))
|
|
slog.InfoContext(ctx, "write", slog.String("path", o))
|
|
f, err := os.Create(o)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
return avif.Encode(f, r)
|
|
})
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func dlimg(ctx context.Context, url string) (image.Image, error) {
|
|
slog.InfoContext(ctx, "download", slog.String("url", url))
|
|
ctx, stop := context.WithTimeout(ctx, 5*time.Second)
|
|
defer stop()
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
slog.InfoContext(ctx, "decode", slog.String("url", url))
|
|
img, _, err := image.Decode(resp.Body)
|
|
return img, err
|
|
}
|
|
|
|
func resize(img image.Image, sizes ...image.Point) []image.Image {
|
|
rs := make([]image.Image, 0, len(sizes))
|
|
for _, sz := range sizes {
|
|
d := image.NewNRGBA(image.Rectangle{Min: image.Point{}, Max: sz})
|
|
draw.CatmullRom.Scale(d, d.Bounds(), img, img.Bounds(), draw.Src, nil)
|
|
rs = append(rs, d)
|
|
}
|
|
return rs
|
|
}
|