Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout. Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
2.4 KiB
Go
106 lines
2.4 KiB
Go
package entity
|
||
|
||
import (
|
||
"image"
|
||
"image/color"
|
||
"image/draw"
|
||
)
|
||
|
||
const (
|
||
collageSize = 480
|
||
collageGap = 8
|
||
)
|
||
|
||
var (
|
||
collageBG = color.RGBA{R: 237, G: 237, B: 237, A: 255}
|
||
collageFill = color.RGBA{R: 216, G: 216, B: 216, A: 255}
|
||
)
|
||
|
||
// PlaceholderFace is a solid tile used when a member avatar cannot be loaded.
|
||
func PlaceholderFace() *image.RGBA {
|
||
img := image.NewRGBA(image.Rect(0, 0, 256, 256))
|
||
draw.Draw(img, img.Bounds(), &image.Uniform{C: collageFill}, image.Point{}, draw.Src)
|
||
return img
|
||
}
|
||
|
||
// Collage tiles as many member avatars as given (1–9). Two people are two tiles, not a 3×3 grid.
|
||
func Collage(faces []image.Image) *image.RGBA {
|
||
if len(faces) > MaxCollageFaces {
|
||
faces = faces[:MaxCollageFaces]
|
||
}
|
||
dst := image.NewRGBA(image.Rect(0, 0, collageSize, collageSize))
|
||
draw.Draw(dst, dst.Bounds(), &image.Uniform{C: collageBG}, image.Point{}, draw.Src)
|
||
cells := collageCells(len(faces))
|
||
for i := range cells {
|
||
r := cells[i]
|
||
src := image.Image(&image.Uniform{C: collageFill})
|
||
if i < len(faces) && faces[i] != nil {
|
||
src = faces[i]
|
||
}
|
||
blitSquare(dst, src, r)
|
||
}
|
||
return dst
|
||
}
|
||
|
||
func collageCells(n int) []image.Rectangle {
|
||
if n < 1 {
|
||
return nil
|
||
}
|
||
if n > MaxCollageFaces {
|
||
n = MaxCollageFaces
|
||
}
|
||
cols := 1
|
||
if n >= 5 {
|
||
cols = 3
|
||
} else if n >= 2 {
|
||
cols = 2
|
||
}
|
||
rows := (n + cols - 1) / cols
|
||
cell := (collageSize - collageGap*(cols+1)) / cols
|
||
gridW := cols*cell + collageGap*(cols-1)
|
||
gridH := rows*cell + collageGap*(rows-1)
|
||
ox := (collageSize - gridW) / 2
|
||
oy := (collageSize - gridH) / 2
|
||
first := n % cols
|
||
if first == 0 {
|
||
first = cols
|
||
}
|
||
out := make([]image.Rectangle, 0, n)
|
||
i := 0
|
||
for row := 0; row < rows && i < n; row++ {
|
||
count := cols
|
||
if row == 0 {
|
||
count = first
|
||
}
|
||
rowW := count*cell + collageGap*(count-1)
|
||
x0 := ox + (gridW-rowW)/2
|
||
y := oy + row*(cell+collageGap)
|
||
for c := 0; c < count && i < n; c++ {
|
||
x := x0 + c*(cell+collageGap)
|
||
out = append(out, image.Rect(x, y, x+cell, y+cell))
|
||
i++
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func blitSquare(dst *image.RGBA, src image.Image, r image.Rectangle) {
|
||
sb := src.Bounds()
|
||
side := sb.Dx()
|
||
if h := sb.Dy(); h < side {
|
||
side = h
|
||
}
|
||
if side < 1 || r.Empty() {
|
||
return
|
||
}
|
||
sx0 := sb.Min.X + (sb.Dx()-side)/2
|
||
sy0 := sb.Min.Y + (sb.Dy()-side)/2
|
||
w, h := r.Dx(), r.Dy()
|
||
for y := 0; y < h; y++ {
|
||
sy := sy0 + y*side/h
|
||
for x := 0; x < w; x++ {
|
||
dst.Set(r.Min.X+x, r.Min.Y+y, src.At(sx0+x*side/w, sy))
|
||
}
|
||
}
|
||
}
|