126 lines
2.7 KiB
Go
126 lines
2.7 KiB
Go
package gallery
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"math"
|
|
|
|
"github.com/InfinityTools/go-binpack2d"
|
|
)
|
|
|
|
type Gallery struct {
|
|
GridWith int
|
|
GridHeight int
|
|
Images []Image
|
|
}
|
|
type Set struct {
|
|
SM Gallery
|
|
LG Gallery
|
|
XLG Gallery
|
|
}
|
|
|
|
func (s *Service) GetGallerySet(ctx context.Context) (gallerySet Set, err error) {
|
|
gallerySet.SM, err = s.GetGallery(ctx, 50)
|
|
if err != nil {
|
|
return
|
|
}
|
|
gallerySet.LG, err = s.GetGallery(ctx, 80)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
gallerySet.XLG, err = s.GetGallery(ctx, 120)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *Service) GetGallery(ctx context.Context, width int) (gallery Gallery, err error) {
|
|
imgs, err := s.GetImages(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
packer := binpack2d.Create(width, math.MaxInt)
|
|
|
|
for len(imgs) > 0 {
|
|
unused := make([]Image, 0, len(imgs))
|
|
for _, img := range imgs {
|
|
adjustImageSizeStrictRatio(&img, 200)
|
|
if img.Height == 0 || img.Width == 0 {
|
|
slog.Warn("image has invalid size", "img", img)
|
|
continue
|
|
}
|
|
|
|
rect, ok := packer.Insert(img.Width, img.Height, binpack2d.RULE_BEST_SHORT_SIDE_FIT)
|
|
if !ok {
|
|
unused = append(unused, img)
|
|
continue
|
|
}
|
|
|
|
img.X = rect.X
|
|
img.Y = rect.Y
|
|
|
|
gallery.Images = append(gallery.Images, img)
|
|
}
|
|
imgs = unused
|
|
}
|
|
|
|
packer.ShrinkBin(false)
|
|
|
|
gallery.GridHeight = packer.GetHeight()
|
|
gallery.GridWith = packer.GetWidth()
|
|
|
|
return
|
|
}
|
|
|
|
// getCleanRatio returns the simplified aspect ratio as (numerator, denominator).
|
|
// It searches for the best fit within a denominator limit to account for rounding errors.
|
|
func getCleanRatio(width, height int) (int, int) {
|
|
targetRatio := float64(width) / float64(height)
|
|
const maxDenominator = 100
|
|
|
|
bestN, bestD := 0, 1
|
|
minDiff := math.MaxFloat64
|
|
|
|
for d := 1; d <= maxDenominator; d++ {
|
|
// Calculate the closest numerator for this denominator
|
|
n := int(math.Round(float64(d) * targetRatio))
|
|
|
|
// Calculate the difference
|
|
currentRatio := float64(n) / float64(d)
|
|
diff := math.Abs(currentRatio - targetRatio)
|
|
|
|
// Keep track of the one with the smallest error
|
|
if diff < minDiff {
|
|
minDiff = diff
|
|
bestN, bestD = n, d
|
|
}
|
|
}
|
|
|
|
return bestN, bestD
|
|
}
|
|
|
|
func adjustImageSizeStrictRatio(img *Image, targetArea int) {
|
|
baseW, baseH := getCleanRatio(img.Width, img.Height)
|
|
|
|
baseArea := baseW * baseH
|
|
|
|
idealK := math.Sqrt(float64(targetArea) / float64(baseArea))
|
|
|
|
k := int(math.Floor(idealK))
|
|
|
|
if math.Abs(float64(k*k*baseArea-targetArea)) >
|
|
math.Abs(float64(int(math.Ceil(idealK))*int(math.Ceil(idealK))*baseArea-targetArea)) {
|
|
k = int(math.Ceil(idealK))
|
|
}
|
|
|
|
slog.Debug("adslkfj", "k", k, "idealK", idealK, "baseH", baseH, "baseW", baseW, "imgW", img.Width, "imgH", img.Height)
|
|
|
|
// 5. Apply the scaled dimensions (guarantees perfect aspect ratio)
|
|
img.Width = baseW * k
|
|
img.Height = baseH * k
|
|
}
|