First commit

This commit is contained in:
proitlab committed 2026-06-30 18:49:39 +07:00
1 parent 4f70c7f8d5
commit dc8dcca75e
17 files changed
+4382 -2

No files matched your search

+241
View File
@@ -0,0 +1,241 @@
package drawing
import (
"fmt"
"image"
"image/color"
"math"
"time"
"gocv.io/x/gocv"
)
const (
CrossFlashFrames = 12
PopupLifetime = 20
LinePulseFrames = 12
CountPulseFrames = 15
BatchPulseFrames = 20
)
var skeleton = [][2]int{{0, 1}, {4, 3}, {1, 2}, {3, 2}, {2, 6}, {2, 5}, {2, 7}, {7, 8}}
var skColors = []color.RGBA{
{0, 255, 255, 255},
{0, 255, 255, 255},
{255, 0, 255, 255},
{255, 0, 255, 255},
{0, 255, 0, 255},
{255, 255, 0, 255},
{0, 0, 255, 255},
{200, 200, 0, 255},
}
var cPanel = color.RGBA{28, 24, 18, 255}
var cBorder = color.RGBA{90, 85, 75, 255}
var cAccent = color.RGBA{255, 200, 60, 255}
var cGreen = color.RGBA{80, 220, 100, 255}
var cText = color.RGBA{220, 220, 220, 255}
var cMuted = color.RGBA{150, 150, 150, 255}
var cAyamBox = color.RGBA{0, 165, 255, 255}
var cTalenanBox = color.RGBA{220, 120, 60, 255}
var cLineCore = color.RGBA{180, 220, 255, 255}
var cLineGlow = color.RGBA{100, 160, 220, 255}
type Popup struct {
X, Y int
Born int
Text string
}
func ResolveLineX(w int, lineX *int, lineXFrac float64) int {
if lineX != nil {
return *lineX
}
if lineXFrac != 0.5 {
return int(float64(w) * lineXFrac)
}
return w / 2
}
func CrossedLine(prevCX, cx float64, lineX int, direction string) bool {
lx := float64(lineX)
switch direction {
case "ltr":
return prevCX < lx && lx <= cx
case "both":
return (prevCX > lx && lx >= cx) || (prevCX < lx && lx <= cx)
default:
return prevCX > lx && lx >= cx
}
}
func OverlayRect(img *gocv.Mat, x1, y1, x2, y2 int, clr color.RGBA, alpha float64) {
x1 = clampI(x1, 0, img.Cols())
y1 = clampI(y1, 0, img.Rows())
x2 = clampI(x2, 0, img.Cols())
y2 = clampI(y2, 0, img.Rows())
if x2 <= x1 || y2 <= y1 {
return
}
roi := img.Region(image.Rect(x1, y1, x2, y2))
defer roi.Close()
overlay := gocv.NewMatWithSize(y2-y1, x2-x1, gocv.MatTypeCV8UC3)
defer overlay.Close()
overlay.SetTo(gocv.NewScalar(float64(clr.B), float64(clr.G), float64(clr.R), 0))
gocv.AddWeighted(overlay, alpha, roi, 1-alpha, 0, &roi)
}
func DrawPill(img *gocv.Mat, text string, x, y int, bg color.RGBA) {
fontScale := 0.45
size := gocv.GetTextSize(text, gocv.FontHersheySimplex, fontScale, 1)
padX, padY := 6, 4
x1 := x
y1 := y - size.Y - padY
x2 := x + size.X + padX*2
y2 := y + padY - 1
gocv.Rectangle(img, image.Rect(x1, y1, x2, y2), bg, -1)
gocv.Rectangle(img, image.Rect(x1, y1, x2, y2), cBorder, 1)
gocv.PutText(img, text, image.Point{X: x + padX, Y: y}, gocv.FontHersheySimplex, fontScale, cText, 1)
}
func DrawCountingLine(img *gocv.Mat, lineX, h, pulse int) {
strength := float64(pulse) / float64(max(LinePulseFrames, 1))
glowAlpha := 0.12 + 0.18*strength
for _, offset := range []int{14, 9, 5} {
alpha := uint8(glowAlpha * 255)
clr := color.RGBA{cLineGlow.R, cLineGlow.G, cLineGlow.B, alpha}
gocv.Line(img, image.Point{X: lineX - offset, Y: 0}, image.Point{X: lineX - offset, Y: h}, clr, 1)
gocv.Line(img, image.Point{X: lineX + offset, Y: 0}, image.Point{X: lineX + offset, Y: h}, clr, 1)
}
dashLen, gap := 18, 12
for y := 0; y < h; y += dashLen + gap {
yEnd := y + dashLen
if yEnd > h {
yEnd = h
}
gocv.Line(img, image.Point{X: lineX, Y: y}, image.Point{X: lineX, Y: yEnd}, cLineCore, 2)
}
gocv.PutText(img, "COUNT LINE", image.Point{X: lineX - 46, Y: 24}, gocv.FontHersheySimplex, 0.42, cLineCore, 1)
}
func DrawHeroCount(img *gocv.Mat, lineX, h, count, pulse int) {
text := fmt.Sprintf("%d", count)
boost := 0.35 * float64(pulse) / float64(max(CountPulseFrames, 1))
fontScale := 1.6 + boost
thickness := 3
size := gocv.GetTextSize(text, gocv.FontHersheySimplex, fontScale, thickness)
pad := 14
tx := lineX - size.X/2
ty := h/2 + size.Y/2
OverlayRect(img, tx-pad, ty-size.Y-pad, tx+size.X+pad, ty+pad/2, cPanel, 0.78)
gocv.Rectangle(img, image.Rect(tx-pad, ty-size.Y-pad, tx+size.X+pad, ty+pad/2), cLineCore, 2)
gocv.PutText(img, text, image.Point{X: tx, Y: ty}, gocv.FontHersheySimplex, fontScale, cGreen, thickness)
}
func DrawHUD(img *gocv.Mat, w int, batchNum, batchCount, totalAyam int, elapsedSec float64, rate float64, cameraID string) {
barH := 52
OverlayRect(img, 0, 0, w, barH, cPanel, 0.72)
gocv.Line(img, image.Point{X: 0, Y: barH}, image.Point{X: w, Y: barH}, cBorder, 1)
gocv.PutText(img, "BATCH", image.Point{X: 16, Y: 20}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
batchLabel := "--"
if batchNum > 0 {
batchLabel = fmt.Sprintf("%d", batchNum)
}
gocv.PutText(img, batchLabel, image.Point{X: 16, Y: 44}, gocv.FontHersheySimplex, 0.9, cAccent, 2)
gocv.PutText(img, "COUNT", image.Point{X: 100, Y: 20}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
gocv.PutText(img, fmt.Sprintf("%d", batchCount), image.Point{X: 100, Y: 44}, gocv.FontHersheySimplex, 0.9, cGreen, 2)
gocv.PutText(img, "TOTAL", image.Point{X: 190, Y: 20}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
gocv.PutText(img, fmt.Sprintf("%d", totalAyam), image.Point{X: 190, Y: 44}, gocv.FontHersheySimplex, 0.7, cText, 1)
gocv.PutText(img, "UPTIME", image.Point{X: 280, Y: 20}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
gocv.PutText(img, fmt.Sprintf("%.1fh", elapsedSec/3600), image.Point{X: 280, Y: 44}, gocv.FontHersheySimplex, 0.7, cText, 1)
gocv.PutText(img, "RATE", image.Point{X: 380, Y: 20}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
gocv.PutText(img, fmt.Sprintf("%.1f/min", rate), image.Point{X: 380, Y: 44}, gocv.FontHersheySimplex, 0.7, cAccent, 1)
gocv.PutText(img, fmt.Sprintf("CAM %s", cameraID), image.Point{X: w - 180, Y: 20}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
}
func DrawFooter(img *gocv.Mat, w, h, frameIdx int, liveTag string) {
barH := 28
OverlayRect(img, 0, h-barH, w, h, cPanel, 0.55)
gocv.PutText(img, fmt.Sprintf("%s | Frame %d", liveTag, frameIdx), image.Point{X: 12, Y: h - 9}, gocv.FontHersheySimplex, 0.45, cMuted, 1)
}
func DrawSkeleton(img *gocv.Mat, kpts [][2]float64) {
for i, pair := range skeleton {
a, b := pair[0], pair[1]
if a >= len(kpts) || b >= len(kpts) {
continue
}
xa, ya := int(kpts[a][0]), int(kpts[a][1])
xb, yb := int(kpts[b][0]), int(kpts[b][1])
if xa > 0 && ya > 0 && xb > 0 && yb > 0 {
clr := cGreen
if i < len(skColors) {
clr = skColors[i]
}
gocv.Line(img, image.Point{X: xa, Y: ya}, image.Point{X: xb, Y: yb}, clr, 3)
}
}
for _, kp := range kpts {
x, y := int(kp[0]), int(kp[1])
if x > 0 && y > 0 {
gocv.Circle(img, image.Point{X: x, Y: y}, 6, color.RGBA{255, 255, 255, 255}, -1)
gocv.Circle(img, image.Point{X: x, Y: y}, 6, color.RGBA{40, 40, 40, 255}, 2)
}
}
}
func DrawPopups(img *gocv.Mat, popups []Popup, frameIdx int) []Popup {
var alive []Popup
for _, pop := range popups {
age := frameIdx - pop.Born
if age > PopupLifetime {
continue
}
alive = append(alive, pop)
fade := 1.0 - float64(age)/PopupLifetime
yy := pop.Y - int(float64(age)*1.8)
clr := color.RGBA{
uint8(float64(cGreen.R) * fade),
uint8(float64(cGreen.G) * fade),
uint8(float64(cGreen.B) * fade),
255,
}
gocv.PutText(img, pop.Text, image.Point{X: pop.X, Y: yy}, gocv.FontHersheySimplex, 0.7, clr, 2)
}
return alive
}
func DrawBatchBanner(img *gocv.Mat, w, batchNum, pulse int) {
if pulse <= 0 {
return
}
text := fmt.Sprintf("NEW BATCH %d", batchNum)
size := gocv.GetTextSize(text, gocv.FontHersheySimplex, 0.8, 2)
x1 := w/2 - size.X/2 - 16
y1 := 62
x2 := w/2 + size.X/2 + 16
y2 := 62 + size.Y + 20
OverlayRect(img, x1, y1, x2, y2, cPanel, 0.7)
gocv.Rectangle(img, image.Rect(x1, y1, x2, y2), cAccent, 2)
gocv.PutText(img, text, image.Point{X: w/2 - size.X/2, Y: 62 + size.Y + 4}, gocv.FontHersheySimplex, 0.8, cAccent, 2)
}
func NowStr() string {
return time.Now().Format("2006-01-02 15:04:05")
}
func clampI(v, lo, hi int) int {
return int(math.Max(float64(lo), math.Min(float64(hi), float64(v))))
}