From c678c7aa454b576aa11fb0f7c896b95b7153a53b Mon Sep 17 00:00:00 2001 From: schreifuchs Date: Mon, 8 Jun 2026 11:18:44 +0200 Subject: [PATCH] fix(gallery): use correct aspect ratios --- README.md | 1 - internal/components/gallery/controller.go | 65 ++++++++++++++++++----- internal/components/gallery/image.go | 14 +++-- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c9bf8f7..9d155e1 100644 --- a/README.md +++ b/README.md @@ -91,4 +91,3 @@ To optimize images before uploading to Nextcloud: ```bash convert input.png -resize 3000 -quality 80 output.webp ``` - diff --git a/internal/components/gallery/controller.go b/internal/components/gallery/controller.go index 90adfe8..1dd8d16 100644 --- a/internal/components/gallery/controller.go +++ b/internal/components/gallery/controller.go @@ -2,6 +2,7 @@ package gallery import ( "context" + "log/slog" "math" "github.com/InfinityTools/go-binpack2d" @@ -42,12 +43,17 @@ func (s *Service) GetGallery(ctx context.Context, width int) (gallery Gallery, e return } - packer := binpack2d.Create(width, 999999) + packer := binpack2d.Create(width, math.MaxInt) for len(imgs) > 0 { unused := make([]Image, 0, len(imgs)) for _, img := range imgs { - adjustImageSize(&img, 200) + 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) @@ -70,17 +76,50 @@ func (s *Service) GetGallery(ctx context.Context, width int) (gallery Gallery, e return } -// adjustImageSize adjusts the image size so that the aspect ratio stays the same -// but the image area is the given parameter. -func adjustImageSize(img *Image, area int) { - // Calculate the original aspect ratio (W / H) - aspectRatio := float64(img.Width) / float64(img.Height) +// 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 - // Calculate new height and width using the derived formulas - newHeight := math.Sqrt(float64(area) / aspectRatio) - newWidth := newHeight * aspectRatio + bestN, bestD := 0, 1 + minDiff := math.MaxFloat64 - // Round to the nearest integer to minimize precision loss - img.Width = int(math.Round(newWidth)) - img.Height = int(math.Round(newHeight)) + 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 } diff --git a/internal/components/gallery/image.go b/internal/components/gallery/image.go index 5a48548..c249d7a 100644 --- a/internal/components/gallery/image.go +++ b/internal/components/gallery/image.go @@ -45,7 +45,6 @@ func (s *Service) assembleImageList(ctx context.Context) error { if err != nil { return fmt.Errorf("could not read contents of %s: %w", s.cfg.Path, err) } - slog.Debug("loaded files", "files", files) savedHash, _ := s.cache.Do(ctx, s.cache.B().Get().Key(hashKey).Build()).AsBytes() @@ -77,10 +76,19 @@ func (s *Service) assembleImageList(ctx context.Context) error { continue } + w := img.Bounds().Dx() + h := img.Bounds().Dy() + + // FIX 2: Defensive check to guard against unreadable/corrupted image boundaries + if w <= 0 || h <= 0 { + slog.Warn("skipping image with zero or negative dimensions", "path", imagePath, "w", w, "h", h) + continue + } + imgs = append(imgs, Image{ UID: uid, - Width: img.Bounds().Dx(), - Height: img.Bounds().Dy(), + Width: w, + Height: h, }) }