Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72bc974aa6 | ||
|
|
c678c7aa45 | ||
|
|
763163c24e |
No files matched your search
@@ -91,4 +91,3 @@ To optimize images before uploading to Nextcloud:
|
|||||||
```bash
|
```bash
|
||||||
convert input.png -resize 3000 -quality 80 output.webp
|
convert input.png -resize 3000 -quality 80 output.webp
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@ package gallery
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log/slog"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/InfinityTools/go-binpack2d"
|
"github.com/InfinityTools/go-binpack2d"
|
||||||
@@ -42,12 +43,17 @@ func (s *Service) GetGallery(ctx context.Context, width int) (gallery Gallery, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
packer := binpack2d.Create(width, 999999)
|
packer := binpack2d.Create(width, math.MaxInt)
|
||||||
|
|
||||||
for len(imgs) > 0 {
|
for len(imgs) > 0 {
|
||||||
unused := make([]Image, 0, len(imgs))
|
unused := make([]Image, 0, len(imgs))
|
||||||
for _, img := range 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)
|
rect, ok := packer.Insert(img.Width, img.Height, binpack2d.RULE_BEST_SHORT_SIDE_FIT)
|
||||||
if !ok {
|
if !ok {
|
||||||
unused = append(unused, img)
|
unused = append(unused, img)
|
||||||
@@ -70,17 +76,50 @@ func (s *Service) GetGallery(ctx context.Context, width int) (gallery Gallery, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjustImageSize adjusts the image size so that the aspect ratio stays the same
|
// getCleanRatio returns the simplified aspect ratio as (numerator, denominator).
|
||||||
// but the image area is the given parameter.
|
// It searches for the best fit within a denominator limit to account for rounding errors.
|
||||||
func adjustImageSize(img *Image, area int) {
|
func getCleanRatio(width, height int) (int, int) {
|
||||||
// Calculate the original aspect ratio (W / H)
|
targetRatio := float64(width) / float64(height)
|
||||||
aspectRatio := float64(img.Width) / float64(img.Height)
|
const maxDenominator = 100
|
||||||
|
|
||||||
// Calculate new height and width using the derived formulas
|
bestN, bestD := 0, 1
|
||||||
newHeight := math.Sqrt(float64(area) / aspectRatio)
|
minDiff := math.MaxFloat64
|
||||||
newWidth := newHeight * aspectRatio
|
|
||||||
|
|
||||||
// Round to the nearest integer to minimize precision loss
|
for d := 1; d <= maxDenominator; d++ {
|
||||||
img.Width = int(math.Round(newWidth))
|
// Calculate the closest numerator for this denominator
|
||||||
img.Height = int(math.Round(newHeight))
|
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
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images"
|
||||||
"github.com/valkey-io/valkey-go"
|
"github.com/valkey-io/valkey-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ type Image struct {
|
|||||||
Height int
|
Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i Image) URL() string {
|
||||||
|
return images.URLFromUID(i.UID)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
imagesKey = "gallery:images:list"
|
imagesKey = "gallery:images:list"
|
||||||
hashKey = "gallery:images:hash"
|
hashKey = "gallery:images:hash"
|
||||||
@@ -45,7 +50,6 @@ func (s *Service) assembleImageList(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not read contents of %s: %w", s.cfg.Path, err)
|
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()
|
savedHash, _ := s.cache.Do(ctx, s.cache.B().Get().Key(hashKey).Build()).AsBytes()
|
||||||
|
|
||||||
@@ -77,10 +81,19 @@ func (s *Service) assembleImageList(ctx context.Context) error {
|
|||||||
continue
|
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{
|
imgs = append(imgs, Image{
|
||||||
UID: uid,
|
UID: uid,
|
||||||
Width: img.Bounds().Dx(),
|
Width: w,
|
||||||
Height: img.Bounds().Dy(),
|
Height: h,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ func UIDFromPath(path string) (uid string, err error) {
|
|||||||
|
|
||||||
func URLFromPath(path string) (url string, err error) {
|
func URLFromPath(path string) (url string, err error) {
|
||||||
uid, err := UIDFromPath(path)
|
uid, err := UIDFromPath(path)
|
||||||
|
url = URLFromUID(uid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func URLFromUID(uid string) (url string) {
|
||||||
url = "/images/" + uid
|
url = "/images/" + uid
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package restgallery
|
package restgallery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/pkg/templer"
|
||||||
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/pages"
|
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web/pages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const preloadCount = 6
|
||||||
|
|
||||||
func (h *Handler) gallery(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) gallery(w http.ResponseWriter, r *http.Request) {
|
||||||
imgs, err := h.srv.GetGallerySet(r.Context())
|
imgs, err := h.srv.GetGallerySet(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -14,5 +18,14 @@ func (h *Handler) gallery(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.r.Render(r.Context(), w, r, pages.GallerySet(imgs))
|
ctx := r.Context()
|
||||||
|
for i, image := range imgs.LG.Images {
|
||||||
|
if i > preloadCount {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
slog.Info("preloading images", "img", image.UID)
|
||||||
|
ctx = templer.AppendHeader(ctx, fmt.Sprintf(`<link rel="preload" href="%s" as="image" />`, image.URL()))
|
||||||
|
}
|
||||||
|
|
||||||
|
h.r.Render(ctx, w, r, pages.GallerySet(imgs))
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user