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 }