Files
Devour/internal/quality/scorecard/draw.go
T
Tomas Dvorak 55885a0e8f first commit
2026-02-22 10:42:17 +01:00

230 lines
5.4 KiB
Go

package scorecard
import (
"image"
"image/color"
"image/draw"
"strconv"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
type DrawContext struct {
Img *image.RGBA
Scale int
}
func NewDrawContext(img *image.RGBA, scale int) *DrawContext {
return &DrawContext{Img: img, Scale: scale}
}
func (dc *DrawContext) S(v int) int {
return v * dc.Scale
}
func (dc *DrawContext) FillRect(x, y, w, h int, c color.RGBA) {
for dy := 0; dy < h; dy++ {
for dx := 0; dx < w; dx++ {
px, py := x+dx, y+dy
if px >= 0 && px < dc.Img.Bounds().Dx() && py >= 0 && py < dc.Img.Bounds().Dy() {
dc.Img.Set(px, py, c)
}
}
}
}
func (dc *DrawContext) DrawRect(x1, y1, x2, y2 int, c color.RGBA, width int) {
for i := 0; i < width; i++ {
dc.DrawHLine(x1, y1+i, x2, c)
dc.DrawHLine(x1, y2-i, x2, c)
dc.DrawVLine(x1+i, y1, y2, c)
dc.DrawVLine(x2-i, y1, y2, c)
}
}
func (dc *DrawContext) DrawHLine(x1, y, x2 int, c color.RGBA) {
if y < 0 || y >= dc.Img.Bounds().Dy() {
return
}
if x1 > x2 {
x1, x2 = x2, x1
}
for x := x1; x <= x2; x++ {
if x >= 0 && x < dc.Img.Bounds().Dx() {
dc.Img.Set(x, y, c)
}
}
}
func (dc *DrawContext) DrawVLine(x, y1, y2 int, c color.RGBA) {
if x < 0 || x >= dc.Img.Bounds().Dx() {
return
}
if y1 > y2 {
y1, y2 = y2, y1
}
for y := y1; y <= y2; y++ {
if y >= 0 && y < dc.Img.Bounds().Dy() {
dc.Img.Set(x, y, c)
}
}
}
func (dc *DrawContext) DrawRoundedRect(x, y, w, h, r int, c color.RGBA) {
dc.FillRect(x+r, y, w-2*r, h, c)
dc.FillRect(x, y+r, w, h-2*r, c)
for dy := -r; dy <= 0; dy++ {
for dx := -r; dx <= 0; dx++ {
if dx*dx+dy*dy >= r*r {
continue
}
dc.Img.Set(x+r+dx, y+r+dy, c)
dc.Img.Set(x+w-r-1-dx, y+r+dy, c)
dc.Img.Set(x+r+dx, y+h-r-1-dy, c)
dc.Img.Set(x+w-r-1-dx, y+h-r-1-dy, c)
}
}
}
func (dc *DrawContext) DrawRoundedRectWithOutline(x, y, w, h, r int, fill, outline color.RGBA, outlineWidth int) {
dc.DrawRoundedRect(x, y, w, h, r, fill)
rr := r - outlineWidth
if rr < 0 {
rr = 0
}
for i := 0; i < outlineWidth; i++ {
ri := r - i
if ri < 0 {
ri = 0
}
dc.DrawHLine(x+ri, y+i, x+w-ri-1, outline)
dc.DrawHLine(x+ri, y+h-i-1, x+w-ri-1, outline)
dc.DrawVLine(x+i, y+ri, y+h-ri-1, outline)
dc.DrawVLine(x+w-i-1, y+ri, y+h-ri-1, outline)
}
}
func (dc *DrawContext) DrawDiamond(cx, cy, size int, c color.RGBA) {
for dy := -size; dy <= size; dy++ {
for dx := -size; dx <= size; dx++ {
if abs(dx)+abs(dy) <= size {
px, py := cx+dx, cy+dy
if px >= 0 && px < dc.Img.Bounds().Dx() && py >= 0 && py < dc.Img.Bounds().Dy() {
dc.Img.Set(px, py, c)
}
}
}
}
}
func (dc *DrawContext) DrawRuleWithOrnament(y, x1, x2, cx int, lineColor, ornamentColor color.RGBA) {
gap := dc.S(8)
dc.DrawHLine(x1, y, cx-gap, lineColor)
dc.DrawHLine(cx+gap, y, x2, lineColor)
dc.DrawDiamond(cx, y, dc.S(3), ornamentColor)
}
func (dc *DrawContext) DrawVertRuleWithOrnament(x, y1, y2, cy int, lineColor, ornamentColor color.RGBA) {
gap := dc.S(8)
dc.DrawVLine(x, y1, cy-gap, lineColor)
dc.DrawVLine(x, cy+gap, y2, lineColor)
dc.DrawDiamond(x, cy, dc.S(3), ornamentColor)
}
func (dc *DrawContext) DrawText(text string, x, y int, face font.Face, c color.RGBA) {
d := font.Drawer{
Dst: dc.Img,
Src: &image.Uniform{c},
Face: face,
Dot: fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)},
}
d.DrawString(text)
}
func (dc *DrawContext) DrawCenteredText(text string, cx, y int, face font.Face, c color.RGBA) {
advance := font.MeasureString(face, text)
x := cx - (advance.Ceil() / 2)
dc.DrawText(text, x, y, face, c)
}
func (dc *DrawContext) DrawRightAlignedText(text string, rx, y int, face font.Face, c color.RGBA) {
advance := font.MeasureString(face, text)
x := rx - advance.Ceil()
dc.DrawText(text, x, y, face, c)
}
func (dc *DrawContext) FillBackground(c color.RGBA) {
draw.Draw(dc.Img, dc.Img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src)
}
func (dc *DrawContext) DrawDoubleFrame(x1, y1, x2, y2 int, outerColor, innerColor color.RGBA, outerWidth, innerWidth int) {
dc.DrawRect(x1, y1, x2, y2, outerColor, outerWidth)
innerX1 := x1 + outerWidth + 2
innerY1 := y1 + outerWidth + 2
innerX2 := x2 - outerWidth - 2
innerY2 := y2 - outerWidth - 2
dc.DrawRect(innerX1, innerY1, innerX2, innerY2, innerColor, innerWidth)
}
func (dc *DrawContext) TextWidth(text string, face font.Face) int {
return font.MeasureString(face, text).Ceil()
}
func (dc *DrawContext) TextBounds(text string, face font.Face) (width, height, offsetY int) {
advance := font.MeasureString(face, text)
width = advance.Ceil()
metrics := face.Metrics()
height = (metrics.Ascent + metrics.Descent).Ceil()
offsetY = -metrics.Ascent.Ceil()
return
}
func (dc *DrawContext) TruncateText(text string, maxWidth int, face font.Face) string {
if dc.TextWidth(text, face) <= maxWidth {
return text
}
ellipsis := "…"
ellipsisWidth := dc.TextWidth(ellipsis, face)
for len(text) > 0 {
text = text[:len(text)-1]
if dc.TextWidth(text, face)+ellipsisWidth <= maxWidth {
return text + ellipsis
}
}
return ellipsis
}
func GetFont() font.Face {
return basicfont.Face7x13
}
func FmtScore(score float64) string {
if score == float64(int(score)) {
return strconv.Itoa(int(score))
}
return strconv.FormatFloat(score, 'f', 1, 64)
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}