// Package capture 负责屏幕抓取、显示器信息与坐标换算。 package capture import ( "fmt" "image" "math" "github.com/kbinani/screenshot" "viewsagent/internal/win32" ) // Monitor 是显示器信息(物理像素)。 type Monitor = win32.Monitor // Region 是以物理像素表示的屏幕区域,坐标基于 Windows 虚拟桌面。 type Region struct { X int `json:"x"` Y int `json:"y"` W int `json:"w"` H int `json:"h"` } // Valid 判断区域是否可用于抓屏。 func (r Region) Valid() bool { return r.W > 0 && r.H > 0 } // Rect 转换为 image.Rectangle。 func (r Region) Rect() image.Rectangle { return image.Rect(r.X, r.Y, r.X+r.W, r.Y+r.H) } // Inset 向内收缩 d 像素,避免把边缘干扰一起截进去。 func (r Region) Inset(d int) Region { if d*2 >= r.W || d*2 >= r.H { return r } return Region{X: r.X + d, Y: r.Y + d, W: r.W - 2*d, H: r.H - 2*d} } // String 返回 "1024x576@(0,0)" 形式的描述。 func (r Region) String() string { return fmt.Sprintf("%dx%d@(%d,%d)", r.W, r.H, r.X, r.Y) } // Mapping 把 Fyne 逻辑坐标换算成物理像素。 // 由于窗口尺寸由系统缩放决定,比例按实际窗口尺寸反推,避免 125%/150% 缩放下的偏移。 type Mapping struct { OffsetX float64 // 窗口左上角的物理 X OffsetY float64 // 窗口左上角的物理 Y ScaleX float64 // 物理宽 / 逻辑宽 ScaleY float64 // 物理高 / 逻辑高 } // ToPhysical 把逻辑矩形换算为物理区域。 func (m Mapping) ToPhysical(x, y, w, h float64) Region { // 用 Round 而不是 +0.5 截断:副屏坐标可能为负,截断会向零取整导致偏移。 return Region{ X: int(math.Round(m.OffsetX + x*m.ScaleX)), Y: int(math.Round(m.OffsetY + y*m.ScaleY)), W: int(math.Round(w * m.ScaleX)), H: int(math.Round(h * m.ScaleY)), } } // ToLogical 是 ToPhysical 的逆运算,用于在逻辑坐标系里定位选区。 func (m Mapping) ToLogical(r Region) (x, y, w, h float64) { return (float64(r.X) - m.OffsetX) / m.ScaleX, (float64(r.Y) - m.OffsetY) / m.ScaleY, float64(r.W) / m.ScaleX, float64(r.H) / m.ScaleY } // Screen 是基于 GDI 的抓屏实现。 type Screen struct{} // Grab 抓取指定区域。 func (Screen) Grab(r Region) (image.Image, error) { if !r.Valid() { return nil, fmt.Errorf("抓屏区域无效: %s", r) } img, err := screenshot.CaptureRect(r.Rect()) if err != nil { return nil, fmt.Errorf("抓取区域 %s 失败: %w", r, err) } return img, nil } // Desktop 抓取整个虚拟桌面(所有显示器)。 func (Screen) Desktop() (image.Image, error) { vd := win32.VirtualScreen() if vd.Width() <= 0 || vd.Height() <= 0 { return nil, fmt.Errorf("获取虚拟桌面尺寸失败") } r := Region{X: vd.Left, Y: vd.Top, W: vd.Width(), H: vd.Height()} return Screen{}.Grab(r) } // Monitors 枚举显示器。 func (Screen) Monitors() []Monitor { return win32.Monitors() } // VirtualScreen 返回虚拟桌面矩形。 func (Screen) VirtualScreen() win32.Rect { return win32.VirtualScreen() }