104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
// 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() }
|