Files
deskview/internal/capture/capture.go
T
2026-10-27 17:58:28 +08:00

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() }