fix(server): collage drawScaled uses center-crop instead of stretch
This commit is contained in:
@@ -176,16 +176,43 @@ func fallbackGlyph() image.Image {
|
||||
return img
|
||||
}
|
||||
|
||||
// drawScaled copies src into dst.Rect, scaling with simple nearest-neighbor.
|
||||
// stdlib lacks high-quality scaling; nearest-neighbor is fine for a
|
||||
// 600x600 output where each cell is 300x300 — most album covers are
|
||||
// already 300-1500 pixels and the visual loss is minor.
|
||||
// drawScaled copies src into dst.Rect using a center-cropped "cover" fit
|
||||
// (the same model as BoxFit.cover / object-fit: cover in the clients).
|
||||
// Non-square sources are scaled so the *smaller* destination dimension is
|
||||
// fully filled and the larger axis is center-cropped, preserving aspect
|
||||
// ratio. Without this, banner-shaped or LP-shaped album art stretches in
|
||||
// the cell -- the album-coherent system playlists (new_for_you,
|
||||
// first_listens) make the stretching disproportionately visible because
|
||||
// fewer unique covers contribute, so each warped cell is a quarter of
|
||||
// the collage rather than diluted.
|
||||
//
|
||||
// Scaling itself stays nearest-neighbor -- stdlib lacks high-quality
|
||||
// scaling and dependency cost is unjustified for this 600x600 output.
|
||||
func drawScaled(dst draw.Image, r image.Rectangle, src image.Image) {
|
||||
srcBounds := src.Bounds()
|
||||
srcW := srcBounds.Dx()
|
||||
srcH := srcBounds.Dy()
|
||||
if srcW <= 0 || srcH <= 0 {
|
||||
return
|
||||
}
|
||||
dstW := r.Dx()
|
||||
dstH := r.Dy()
|
||||
// Cover-fit: the side of src that maps to dst at the larger scale
|
||||
// fully fills its axis; the other axis is center-cropped.
|
||||
scaleX := float64(dstW) / float64(srcW)
|
||||
scaleY := float64(dstH) / float64(srcH)
|
||||
scale := scaleX
|
||||
if scaleY > scale {
|
||||
scale = scaleY
|
||||
}
|
||||
cropW := float64(dstW) / scale
|
||||
cropH := float64(dstH) / scale
|
||||
cropOffX := float64(srcBounds.Min.X) + (float64(srcW)-cropW)/2
|
||||
cropOffY := float64(srcBounds.Min.Y) + (float64(srcH)-cropH)/2
|
||||
for y := r.Min.Y; y < r.Max.Y; y++ {
|
||||
sy := int(cropOffY + float64(y-r.Min.Y)*cropH/float64(dstH))
|
||||
for x := r.Min.X; x < r.Max.X; x++ {
|
||||
sx := srcBounds.Min.X + (x-r.Min.X)*srcBounds.Dx()/r.Dx()
|
||||
sy := srcBounds.Min.Y + (y-r.Min.Y)*srcBounds.Dy()/r.Dy()
|
||||
sx := int(cropOffX + float64(x-r.Min.X)*cropW/float64(dstW))
|
||||
dst.Set(x, y, src.At(sx, sy))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user