feat: improved text pos. fallback mechanism
This commit is contained in:
parent
fb8c0f12d5
commit
895f15bdd2
110
banner.go
110
banner.go
@ -40,7 +40,6 @@ func NewTextData(text string, size float64, multiline bool) *TextData {
|
|||||||
return &TextData{splits, size, false, -1, -1, -1, -1}
|
return &TextData{splits, size, false, -1, -1, -1, -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consistent outline thickness based on font size
|
|
||||||
func getOutlineThickness(size float64) int {
|
func getOutlineThickness(size float64) int {
|
||||||
thickness := int(size / 8)
|
thickness := int(size / 8)
|
||||||
if thickness < 1 {
|
if thickness < 1 {
|
||||||
@ -51,7 +50,6 @@ func getOutlineThickness(size float64) int {
|
|||||||
return thickness
|
return thickness
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actual bounding box for text including outline
|
|
||||||
func getTextBounds(text []string, size float64) (width, height int) {
|
func getTextBounds(text []string, size float64) (width, height int) {
|
||||||
face, err := createFontFace(size)
|
face, err := createFontFace(size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,8 +144,7 @@ func drawTextWithOutline(canvas *image.RGBA, text []string, x, y int, textColor,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place all elements to canvas without overlaps
|
func findTextPositions(textData []*TextData, filename string) {
|
||||||
func findTextPositions(textData []*TextData) {
|
|
||||||
padding := 10
|
padding := 10
|
||||||
|
|
||||||
for i, data := range textData {
|
for i, data := range textData {
|
||||||
@ -182,7 +179,7 @@ func findTextPositions(textData []*TextData) {
|
|||||||
y := rand.IntN(maxY-minY+1) + minY
|
y := rand.IntN(maxY-minY+1) + minY
|
||||||
|
|
||||||
// bounding box for cur. text
|
// bounding box for cur. text
|
||||||
currentRect := struct{ x, y, w, h int }{
|
curRect := struct{ x, y, w, h int }{
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
w: w,
|
w: w,
|
||||||
@ -204,7 +201,7 @@ func findTextPositions(textData []*TextData) {
|
|||||||
h: other.H + padding,
|
h: other.H + padding,
|
||||||
}
|
}
|
||||||
|
|
||||||
if rectsOverlap(currentRect, otherRect) {
|
if rectsOverlap(curRect, otherRect) {
|
||||||
overlaps = true
|
overlaps = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -219,35 +216,84 @@ func findTextPositions(textData []*TextData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback: use grid positioning
|
// fallback: place to a free corner
|
||||||
if !placed {
|
if !placed {
|
||||||
log.Warnf("Using fallback position for text '%v'", data.Text)
|
log.Debugf("Using fallback position for text '%s' in '%s'", data.Text, filename)
|
||||||
|
|
||||||
gridCols := 2
|
corners := []struct {
|
||||||
col := i % gridCols
|
name string
|
||||||
row := i / gridCols
|
x, y int
|
||||||
|
}{
|
||||||
cellWidth := config.BannerWidth / gridCols
|
{"top-left", padding, padding},
|
||||||
cellHeight := config.BannerHeight / 3 // NOTE: assuming max 3 rows
|
{"top-right", config.BannerWidth - w - padding, padding},
|
||||||
|
{"bottom-left", padding, config.BannerHeight - h - padding},
|
||||||
data.X = col*cellWidth + (cellWidth-w)/2
|
{"bottom-right", config.BannerWidth - w - padding, config.BannerHeight - h - padding},
|
||||||
data.Y = row*cellHeight + (cellHeight-h)/2
|
|
||||||
|
|
||||||
// ensure within bounds
|
|
||||||
if data.X < padding {
|
|
||||||
data.X = padding
|
|
||||||
}
|
|
||||||
if data.Y < padding {
|
|
||||||
data.Y = padding
|
|
||||||
}
|
|
||||||
if data.X+w > config.BannerWidth-padding {
|
|
||||||
data.X = config.BannerWidth - w - padding
|
|
||||||
}
|
|
||||||
if data.Y+h > config.BannerHeight-padding {
|
|
||||||
data.Y = config.BannerHeight - h - padding
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bestCorner := 0
|
||||||
|
minOverlapArea := int(^uint(0) >> 1) // max. int
|
||||||
|
|
||||||
|
// try each corner and find the one with least overlap
|
||||||
|
for cornerIdx, corner := range corners {
|
||||||
|
curRect := struct{ x, y, w, h int }{
|
||||||
|
x: corner.x,
|
||||||
|
y: corner.y,
|
||||||
|
w: w,
|
||||||
|
h: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
totalOverlapArea := 0
|
||||||
|
|
||||||
|
// calculate total overlap area with existing texts
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
other := textData[j]
|
||||||
|
if !other.Positioned {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
otherRect := struct{ x, y, w, h int }{
|
||||||
|
x: other.X,
|
||||||
|
y: other.Y,
|
||||||
|
w: other.W,
|
||||||
|
h: other.H,
|
||||||
|
}
|
||||||
|
|
||||||
|
if rectsOverlap(curRect, otherRect) {
|
||||||
|
// intersection rectangle
|
||||||
|
overlapLeft := max(curRect.x, otherRect.x)
|
||||||
|
overlapTop := max(curRect.y, otherRect.y)
|
||||||
|
overlapRight := min(curRect.x+curRect.w, otherRect.x+otherRect.w)
|
||||||
|
overlapBottom := min(curRect.y+curRect.h, otherRect.y+otherRect.h)
|
||||||
|
|
||||||
|
overlapWidth := overlapRight - overlapLeft
|
||||||
|
overlapHeight := overlapBottom - overlapTop
|
||||||
|
overlapArea := overlapWidth * overlapHeight
|
||||||
|
|
||||||
|
totalOverlapArea += overlapArea
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalOverlapArea < minOverlapArea {
|
||||||
|
minOverlapArea = totalOverlapArea
|
||||||
|
bestCorner = cornerIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no overlap, use the pos. immediately
|
||||||
|
if totalOverlapArea == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// place at the best corner
|
||||||
|
data.X = corners[bestCorner].x
|
||||||
|
data.Y = corners[bestCorner].y
|
||||||
data.Positioned = true
|
data.Positioned = true
|
||||||
|
|
||||||
|
if minOverlapArea > 0 {
|
||||||
|
log.Debugf("Placed text '%v' at %s corner with some overlap (area: %d)", data.Text, corners[bestCorner].name, minOverlapArea)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Placed text '%v' at %s corner with no overlap", data.Text, corners[bestCorner].name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,9 +409,10 @@ func genPerlinBG() *image.RGBA {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func genBanner(textData []*TextData) error {
|
func genBanner(textData []*TextData) error {
|
||||||
|
fn := fmt.Sprintf("banner_%d.png", time.Now().UnixMicro())
|
||||||
canvas := genPerlinBG()
|
canvas := genPerlinBG()
|
||||||
|
|
||||||
findTextPositions(textData)
|
findTextPositions(textData, fn)
|
||||||
|
|
||||||
for _, td := range textData {
|
for _, td := range textData {
|
||||||
if td.Positioned {
|
if td.Positioned {
|
||||||
@ -374,7 +421,6 @@ func genBanner(textData []*TextData) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := fmt.Sprintf("banner_%d.png", time.Now().UnixMicro())
|
|
||||||
fp := filepath.Join(bannersDir, fn)
|
fp := filepath.Join(bannersDir, fn)
|
||||||
file, err := os.Create(fp)
|
file, err := os.Create(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user