Compare commits

...

2 Commits

Author SHA1 Message Date
AI Engineer
4fd7ffbcf9 chore: infrastructure alignment and doc sync (by AICoder) 2026-05-16 01:25:31 +08:00
AI Engineer
aa56493bb2 chore: infrastructure alignment and doc sync (by checkall) 2026-05-14 21:49:44 +08:00
5 changed files with 43 additions and 36 deletions

21
TODO.md
View File

@ -1,21 +0,0 @@
# @go/vision 演进路线图 (TODO)
## 🎨 1. 水印系统 (Watermarking) - [已完成]
- [x] **图片水印**: 支持 `func (c *Canvas) Watermark(mark *Canvas, pos Position, opacity float64)`
- [x] **文字水印**: 支持指定字体、颜色、旋转角度的快速文字遮盖。
- [x] **视频水印**: 封装 FFmpeg 指令,支持给视频一键添加静态水印。
## 🧩 2. 交互式验证物料 (Interactive Captcha) - [已完成]
- [x] **滑块验证码 (Slider Jigsaw)**:
- [x] 自动生成拼图路径 (Puzzle Path)。
- [x] 输出带阴影的“拼图块”与带“槽口”的底图。
- [x] **开箱即用**: 输出 `JigsawResult` 结构。
## 🎞 3. 动态验证码 (Dynamic GIF) - [已完成]
- [x] **GIF 动态验证码**: 不同帧出现不同字符,并伴随背景波形干扰。
## 🛠 4. 向量路径补完 (Vector)
- [ ] **SVG Path 基础指令**: 实现 `M, L, C, Q, Z` 的解析,支持业务中复杂的异形裁剪(如滑块形状)。
## 基础维护
- [ ] 移除超纲的视频编排计划,保持 FFmpeg 仅用于帧提取与基础合成。

View File

@ -74,3 +74,17 @@ func (c *Canvas) DecodeAll() (string, error) {
// 再尝试条形码
return c.DecodeBarcode()
}
// Recognize 是 DecodeAll 的别名,用于自动识别图片中的任何码 (QR, Barcode)
func (c *Canvas) Recognize() (string, error) {
return c.DecodeAll()
}
// Recognize 从指定路径加载图片并自动识别其中的任何码 (QR, Barcode)
func Recognize(path string) (string, error) {
c, err := Load(path)
if err != nil {
return "", err
}
return c.DecodeAll()
}

10
go.mod
View File

@ -3,9 +3,9 @@ module apigo.cc/go/vision
go 1.25.0
require (
apigo.cc/go/cast v1.3.1
apigo.cc/go/file v1.3.1
apigo.cc/go/rand v1.3.0
apigo.cc/go/cast v1.3.3
apigo.cc/go/file v1.3.2
apigo.cc/go/rand v1.3.1
github.com/boombuler/barcode v1.1.0
github.com/disintegration/imaging v1.6.2
github.com/flopp/go-findfont v0.1.0
@ -16,8 +16,8 @@ require (
)
require (
apigo.cc/go/encoding v1.3.0 // indirect
apigo.cc/go/safe v1.3.0 // indirect
apigo.cc/go/encoding v1.3.1 // indirect
apigo.cc/go/safe v1.3.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/kr/text v0.2.0 // indirect
golang.org/x/crypto v0.51.0 // indirect

20
go.sum
View File

@ -1,13 +1,13 @@
apigo.cc/go/cast v1.3.1 h1:Y64mit3tCtA1gnSaeaPNf9QjvwX1RA+hFc80j/yUMnI=
apigo.cc/go/cast v1.3.1/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
apigo.cc/go/encoding v1.3.0 h1:8jqNHoZBR8vOU/BGsLFebfp1Txa1UxDRpd7YwzIFLJs=
apigo.cc/go/encoding v1.3.0/go.mod h1:kT/uUJiuAOkZ4LzUWrUtk/I0iL1D8aatvD+59bDnHBo=
apigo.cc/go/file v1.3.1 h1:qHgiJsn1K9DazWRrPoHVnXtp6hDGGsUpAE/4G1bFXqY=
apigo.cc/go/file v1.3.1/go.mod h1:pYHBlB/XwsrnWpEh7GIFpbiqobrExfiB+rEN8V2d2kY=
apigo.cc/go/rand v1.3.0 h1:k+UFAhMySwXf+dq8Om9TniZV6fm6gAE0evbrqMEdwQU=
apigo.cc/go/rand v1.3.0/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
apigo.cc/go/safe v1.3.0 h1:uctdAUsphT9p60Tk4oS5xPCe0NoIdOHfsYv4PNS0Rok=
apigo.cc/go/safe v1.3.0/go.mod h1:tC9X14V+qh0BqIrVg4UkXbl+2pEN+lj2ZNI8IjDB6Fs=
apigo.cc/go/cast v1.3.3 h1:aln5eDR5DZVWVzZ/y5SJh1gQNgWv2sT82I25NaO9g34=
apigo.cc/go/cast v1.3.3/go.mod h1:lGlwImiOvHxG7buyMWhFzcdvQzmSaoKbmr7bcDfUpHk=
apigo.cc/go/encoding v1.3.1 h1:y8O58KYAyulkThg1O2ji2BqjnFoSvk42sit9I3z+K7Y=
apigo.cc/go/encoding v1.3.1/go.mod h1:xAJk5b83VZ31mXMTnyp0dfMoBKfT/AHDn0u+cQfojgY=
apigo.cc/go/file v1.3.2 h1:pu4oiDyiqgj3/eykfnJf+/6+A9v/Z0b3ClP5XK+lwG4=
apigo.cc/go/file v1.3.2/go.mod h1:vci4h0Pz94mV6dkniQkuyBYERVYeq7/LX4jJVuCg9hs=
apigo.cc/go/rand v1.3.1 h1:7FvsI6PtQ5XrWER0dTiLVo0p7GIxRidT/TBKhVy93j8=
apigo.cc/go/rand v1.3.1/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
apigo.cc/go/safe v1.3.1 h1:irTCqPAC97gGsX/Lw5AzLelDt1xXLEZIAaVhLELWe9Q=
apigo.cc/go/safe v1.3.1/go.mod h1:XdOpBhN2vkImalaykYXXmEpczqWa1y3ah6/Q72cdRqE=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=

View File

@ -24,6 +24,20 @@ func NewVideo() (*Video, error) {
return &Video{FFmpegPath: p}, nil
}
// Capture 是从视频中提取指定时间的帧的便捷封装。
// 如果未提供 offsetSeconds默认提取第 0 秒。
func Capture(videoPath string, offsetSeconds ...float64) (*Canvas, error) {
v, err := NewVideo()
if err != nil {
return nil, err
}
offset := 0.0
if len(offsetSeconds) > 0 {
offset = offsetSeconds[0]
}
return v.ExtractFrame(videoPath, offset)
}
// ExtractFrame 从视频中提取指定时间的帧
func (v *Video) ExtractFrame(videoPath string, offsetSeconds float64) (*Canvas, error) {
tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("frame_%d.png", os.Getpid()))