iso-bot/pkg/engine/capture/capture.go
Hoid 4f0b84ec31 Fix prototype: calibrate vision for real D2R screenshots, implement orb detection, improve dashboard
- Created debug tool (cmd/debug/main.go) to analyze real D2R screenshots and calibrate HSV ranges
- Fixed HSV color ranges for health/mana orbs based on real screenshot analysis (99.5% and 82% detection rates)
- Replaced ReadBarPercentage with ReadOrbPercentage for circular orbs (not horizontal bars)
- Added SetSource() method to capture Manager for hot-swapping capture sources
- Fixed dashboard JavaScript null reference errors with proper array checks
- Improved dashboard refresh rate from 100ms to 1000ms for better performance
- Added proper error handling for empty/null API responses
- Successfully detecting game state, health (99.5%), and mana (82%) from real D2R screenshot
2026-02-14 10:55:30 +00:00

129 lines
2.9 KiB
Go

// Package capture provides screen capture from various sources.
//
// Supports capturing from:
// - Local window (by title or handle)
// - VM display (VNC, Spice, or VM window on host)
// - Full screen / monitor region
//
// The capture interface is source-agnostic — the engine doesn't care
// where the frames come from.
package capture
import (
"image"
"time"
)
// Region defines a rectangular area to capture.
type Region struct {
X, Y, Width, Height int
}
// Source represents a capture source (window, VM, screen, etc.)
type Source interface {
// Name returns a human-readable description of the source.
Name() string
// Capture grabs a single frame.
Capture() (image.Image, error)
// CaptureRegion grabs a sub-region of the source.
CaptureRegion(r Region) (image.Image, error)
// Size returns the source dimensions.
Size() (width, height int)
// Close releases resources.
Close() error
}
// Stats tracks capture performance metrics.
type Stats struct {
FrameCount uint64
AvgCaptureMs float64
FPS float64
LastCapture time.Time
}
// Manager handles screen capture with performance tracking.
type Manager struct {
source Source
stats Stats
}
// NewManager creates a capture manager with the given source.
func NewManager(source Source) *Manager {
return &Manager{source: source}
}
// Capture grabs a frame and updates performance stats.
func (m *Manager) Capture() (image.Image, error) {
start := time.Now()
frame, err := m.source.Capture()
if err != nil {
return nil, err
}
elapsed := time.Since(start)
m.stats.FrameCount++
m.stats.LastCapture = start
// Rolling average
alpha := 0.1
ms := float64(elapsed.Microseconds()) / 1000.0
if m.stats.FrameCount == 1 {
m.stats.AvgCaptureMs = ms
} else {
m.stats.AvgCaptureMs = m.stats.AvgCaptureMs*(1-alpha) + ms*alpha
}
if m.stats.AvgCaptureMs > 0 {
m.stats.FPS = 1000.0 / m.stats.AvgCaptureMs
}
return frame, nil
}
// CaptureRegion grabs a sub-region.
func (m *Manager) CaptureRegion(r Region) (image.Image, error) {
return m.source.CaptureRegion(r)
}
// Stats returns current capture performance stats.
func (m *Manager) Stats() Stats {
return m.stats
}
// Close releases the capture source.
func (m *Manager) Close() error {
return m.source.Close()
}
// Source returns the underlying capture source.
func (m *Manager) Source() Source {
return m.source
}
// Size returns the source dimensions.
func (m *Manager) Size() (width, height int) {
return m.source.Size()
}
// SetSource swaps the capture source.
// This is useful for development when uploading new screenshots.
func (m *Manager) SetSource(newSource Source) error {
// Close the old source
if m.source != nil {
if err := m.source.Close(); err != nil {
// Log but don't fail - we still want to switch sources
// log.Printf("Warning: failed to close old capture source: %v", err)
}
}
// Switch to new source
m.source = newSource
// Reset stats for the new source
m.stats = Stats{}
return nil
}