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
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawScaled copies src into dst.Rect, scaling with simple nearest-neighbor.
|
// drawScaled copies src into dst.Rect using a center-cropped "cover" fit
|
||||||
// stdlib lacks high-quality scaling; nearest-neighbor is fine for a
|
// (the same model as BoxFit.cover / object-fit: cover in the clients).
|
||||||
// 600x600 output where each cell is 300x300 — most album covers are
|
// Non-square sources are scaled so the *smaller* destination dimension is
|
||||||
// already 300-1500 pixels and the visual loss is minor.
|
// 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) {
|
func drawScaled(dst draw.Image, r image.Rectangle, src image.Image) {
|
||||||
srcBounds := src.Bounds()
|
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++ {
|
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++ {
|
for x := r.Min.X; x < r.Max.X; x++ {
|
||||||
sx := srcBounds.Min.X + (x-r.Min.X)*srcBounds.Dx()/r.Dx()
|
sx := int(cropOffX + float64(x-r.Min.X)*cropW/float64(dstW))
|
||||||
sy := srcBounds.Min.Y + (y-r.Min.Y)*srcBounds.Dy()/r.Dy()
|
|
||||||
dst.Set(x, y, src.At(sx, sy))
|
dst.Set(x, y, src.At(sx, sy))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user