Compare commits
No commits in common. "84e72eec6537f7b79d87d387812d4447f02cda70" and "1645918690600f93fbd070b9f5129ff583421046" have entirely different histories.
84e72eec65
...
1645918690
@ -1,12 +1,5 @@
|
||||
# CHANGELOG - apigo.cc/go/vision
|
||||
|
||||
## v1.5.3 (2026-06-21)
|
||||
- **JS 对齐**: 重构 JS 导出为具名函数,并引入 `jsmod.MakeError` 动态包装错误以获取调用栈。
|
||||
- **依赖更新**: 升级依赖 `jsmod` 至 `v1.5.3`,`cast` 至 `v1.5.3`,`rand` 至 `v1.5.3`,`encoding` 至 `v1.5.4`,`safe` 至 `v1.5.2`,`file` 至 `v1.5.5`。
|
||||
|
||||
## v1.5.2 (2026-06-11)
|
||||
- **版本对齐**: 基础设施全局对齐 v1.5.2。
|
||||
|
||||
## v1.5.1 (2026-06-08)
|
||||
- **JS 对齐 & 智能文档**:
|
||||
- 将所有注册到 `jsmod` 的方法名统一为 PascalCase。
|
||||
|
||||
10
TEST.md
10
TEST.md
@ -37,13 +37,9 @@ go test -bench .
|
||||
|
||||
| 测试项 | 耗时 (ns/op) |
|
||||
| :--- | :--- |
|
||||
| **WarpPerspective** | 8,928,754 |
|
||||
| **PHash** | 1,041,925 |
|
||||
| **ExtractPalette** | 453,400 |
|
||||
|
||||
## 🛡️ 鲁棒性防御 (Robustness)
|
||||
- **可选参数提示**:`New`、`Save` 等方法接受指针作为可选参数,结合最新的 `go/js` 引擎智能过滤空参数。
|
||||
- **JS 错误调用栈**:JS 桥接层改用具名导出并使用 `jsmod.MakeError` 包裹错误,确保 JS 抛出异常时携带准确的 Go 运行时堆栈。
|
||||
| **WarpPerspective** | 7,079,540 |
|
||||
| **PHash** | 958,618 |
|
||||
| **ExtractPalette** | 402,176 |
|
||||
|
||||
---
|
||||
所有测试均遵循 `@go` 基础设施标准,无外部系统依赖(除 FFmpeg 视频测试外,该部分会自动跳过或提示引导)。
|
||||
|
||||
12
go.mod
12
go.mod
@ -3,10 +3,10 @@ module apigo.cc/go/vision
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
apigo.cc/go/cast v1.5.3
|
||||
apigo.cc/go/file v1.5.5
|
||||
apigo.cc/go/jsmod v1.5.3
|
||||
apigo.cc/go/rand v1.5.3
|
||||
apigo.cc/go/cast v1.5.0
|
||||
apigo.cc/go/file v1.5.0
|
||||
apigo.cc/go/jsmod v1.5.0
|
||||
apigo.cc/go/rand v1.5.0
|
||||
github.com/boombuler/barcode v1.1.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/flopp/go-findfont v0.1.0
|
||||
@ -17,8 +17,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
apigo.cc/go/encoding v1.5.4
|
||||
apigo.cc/go/safe v1.5.2
|
||||
apigo.cc/go/encoding v1.5.0 // indirect
|
||||
apigo.cc/go/safe v1.5.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.52.0 // indirect
|
||||
|
||||
200
js_export.go
200
js_export.go
@ -10,167 +10,113 @@ import (
|
||||
func init() {
|
||||
jsmod.Register("vision", map[string]any{
|
||||
// 基础操作
|
||||
"Load": jsLoad,
|
||||
"New": jsNew,
|
||||
"Save": jsSave,
|
||||
|
||||
// 生成类
|
||||
"GenerateQRCode": jsGenerateQRCode,
|
||||
"GenerateBarcode": jsGenerateBarcode,
|
||||
"GenerateCaptcha": jsGenerateCaptcha,
|
||||
|
||||
// 预览类 (直接写文件)
|
||||
"GenerateImagePreview": jsGenerateImagePreview,
|
||||
"GenerateVideoPreview": jsGenerateVideoPreview,
|
||||
|
||||
// 转换类
|
||||
"Convert": jsConvert,
|
||||
"Optimize": jsOptimize,
|
||||
|
||||
// 辅助工具
|
||||
"LoadFonts": jsLoadFonts,
|
||||
"Recognize": jsRecognize,
|
||||
})
|
||||
}
|
||||
|
||||
func jsLoad(ctx context.Context, path string) (*jsCanvas, error) {
|
||||
"Load": func(ctx context.Context, path string) (*jsCanvas, error) {
|
||||
p, err := file.VerifyPathForSafeMode(ctx, path)
|
||||
if err != nil {
|
||||
return nil, jsmod.MakeError(err)
|
||||
return nil, err
|
||||
}
|
||||
c, err := Load(p)
|
||||
if err != nil {
|
||||
return nil, jsmod.MakeError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &jsCanvas{ctx: ctx, c: c}, nil
|
||||
}
|
||||
|
||||
func jsNew(ctx context.Context, w, h int, bg *string) *jsCanvas {
|
||||
},
|
||||
"New": func(ctx context.Context, w, h int, bg *string) *jsCanvas {
|
||||
if bg != nil {
|
||||
return &jsCanvas{ctx: ctx, c: New(w, h, *bg)}
|
||||
}
|
||||
return &jsCanvas{ctx: ctx, c: New(w, h)}
|
||||
}
|
||||
|
||||
func jsSave(ctx context.Context, j *jsCanvas, path string, quality *int) error {
|
||||
},
|
||||
"Save": func(ctx context.Context, j *jsCanvas, path string, quality *int) error {
|
||||
p, err := file.VerifyPathForSafeMode(ctx, path)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
if quality != nil {
|
||||
if err := Save(j.c, p, *quality); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := Save(j.c, p); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
return Save(j.c, p, *quality)
|
||||
}
|
||||
return Save(j.c, p)
|
||||
},
|
||||
|
||||
func jsGenerateQRCode(ctx context.Context, content string, size int) (*jsCanvas, error) {
|
||||
// 生成类
|
||||
"GenerateQRCode": func(ctx context.Context, content string, size int) (*jsCanvas, error) {
|
||||
c, err := GenerateQRCode(content, size)
|
||||
if err != nil {
|
||||
return nil, jsmod.MakeError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &jsCanvas{ctx: ctx, c: c}, nil
|
||||
}
|
||||
|
||||
func jsGenerateBarcode(ctx context.Context, content string, w, h int) (*jsCanvas, error) {
|
||||
},
|
||||
"GenerateBarcode": func(ctx context.Context, content string, w, h int) (*jsCanvas, error) {
|
||||
c, err := GenerateBarcode(content, w, h)
|
||||
if err != nil {
|
||||
return nil, jsmod.MakeError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &jsCanvas{ctx: ctx, c: c}, nil
|
||||
}
|
||||
|
||||
func jsGenerateCaptcha(ctx context.Context, opt *CaptchaOption) *jsCanvas {
|
||||
},
|
||||
"GenerateCaptcha": func(ctx context.Context, opt *CaptchaOption) *jsCanvas {
|
||||
return &jsCanvas{ctx: ctx, c: GenerateCaptcha(opt)}
|
||||
}
|
||||
},
|
||||
|
||||
func jsGenerateImagePreview(ctx context.Context, src, out string, w, h int) error {
|
||||
// 预览类 (直接写文件)
|
||||
"GenerateImagePreview": func(ctx context.Context, src, out string, w, h int) error {
|
||||
pSrc, err := file.VerifyPathForSafeMode(ctx, src)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
pOut, err := file.VerifyPathForSafeMode(ctx, out)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
if err := GenerateImagePreview(pSrc, pOut, w, h); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsGenerateVideoPreview(ctx context.Context, src, out string, w, h int, interval *int) error {
|
||||
return GenerateImagePreview(pSrc, pOut, w, h)
|
||||
},
|
||||
"GenerateVideoPreview": func(ctx context.Context, src, out string, w, h int, interval *int) error {
|
||||
pSrc, err := file.VerifyPathForSafeMode(ctx, src)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
pOut, err := file.VerifyPathForSafeMode(ctx, out)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
if interval != nil {
|
||||
if err := GenerateVideoPreview(pSrc, pOut, w, h, *interval); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := GenerateVideoPreview(pSrc, pOut, w, h); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
return GenerateVideoPreview(pSrc, pOut, w, h, *interval)
|
||||
}
|
||||
return GenerateVideoPreview(pSrc, pOut, w, h)
|
||||
},
|
||||
|
||||
func jsConvert(ctx context.Context, src, dst string, quality *int) error {
|
||||
// 转换类
|
||||
"Convert": func(ctx context.Context, src, dst string, quality *int) error {
|
||||
pSrc, err := file.VerifyPathForSafeMode(ctx, src)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
pDst, err := file.VerifyPathForSafeMode(ctx, dst)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
if quality != nil {
|
||||
if err := Convert(pSrc, pDst, *quality); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return Convert(pSrc, pDst, *quality)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := Convert(pSrc, pDst); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsOptimize(ctx context.Context, path string, maxWidth int, quality int) error {
|
||||
return Convert(pSrc, pDst)
|
||||
},
|
||||
"Optimize": func(ctx context.Context, path string, maxWidth int, quality int) error {
|
||||
p, err := file.VerifyPathForSafeMode(ctx, path)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
if err := Optimize(p, maxWidth, quality); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return Optimize(p, maxWidth, quality)
|
||||
},
|
||||
|
||||
func jsLoadFonts(paths ...string) {
|
||||
LoadFonts(paths...)
|
||||
}
|
||||
|
||||
func jsRecognize(ctx context.Context, path string) (string, error) {
|
||||
// 辅助工具
|
||||
"LoadFonts": LoadFonts,
|
||||
"Recognize": func(ctx context.Context, path string) (string, error) {
|
||||
p, err := file.VerifyPathForSafeMode(ctx, path)
|
||||
if err != nil {
|
||||
return "", jsmod.MakeError(err)
|
||||
return "", err
|
||||
}
|
||||
res, err := Recognize(p)
|
||||
if err != nil {
|
||||
return "", jsmod.MakeError(err)
|
||||
}
|
||||
return res, nil
|
||||
return Recognize(p)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// jsCanvas 包装器,支持链式调用
|
||||
@ -185,18 +131,12 @@ func (j *jsCanvas) Height() int { return j.c.Height() }
|
||||
func (j *jsCanvas) Save(path string, quality *int) error {
|
||||
p, err := file.VerifyPathForSafeMode(j.ctx, path)
|
||||
if err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return err
|
||||
}
|
||||
if quality != nil {
|
||||
if err := Save(j.c, p, *quality); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
return Save(j.c, p, *quality)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := Save(j.c, p); err != nil {
|
||||
return jsmod.MakeError(err)
|
||||
}
|
||||
return nil
|
||||
return Save(j.c, p)
|
||||
}
|
||||
|
||||
// 效果与绘图 (返回自身以支持链式)
|
||||
@ -210,14 +150,8 @@ func (j *jsCanvas) FlipV() *jsCanvas { j.c.FlipV(); return j
|
||||
func (j *jsCanvas) Sharpen(sigma float64) *jsCanvas { j.c.Sharpen(sigma); return j }
|
||||
func (j *jsCanvas) AdjustBrightness(p float64) *jsCanvas { j.c.AdjustBrightness(p); return j }
|
||||
|
||||
func (j *jsCanvas) Rect(x, y, w, h float64, opt *DrawStyle) *jsCanvas {
|
||||
j.c.Rect(x, y, w, h, opt)
|
||||
return j
|
||||
}
|
||||
func (j *jsCanvas) Circle(x, y, r float64, opt *DrawStyle) *jsCanvas {
|
||||
j.c.Circle(x, y, r, opt)
|
||||
return j
|
||||
}
|
||||
func (j *jsCanvas) Rect(x, y, w, h float64, opt *DrawStyle) *jsCanvas { j.c.Rect(x, y, w, h, opt); return j }
|
||||
func (j *jsCanvas) Circle(x, y, r float64, opt *DrawStyle) *jsCanvas { j.c.Circle(x, y, r, opt); return j }
|
||||
func (j *jsCanvas) Line(x1, y1, x2, y2 float64, opt *DrawStyle) *jsCanvas {
|
||||
j.c.Line(x1, y1, x2, y2, opt)
|
||||
return j
|
||||
@ -228,24 +162,6 @@ func (j *jsCanvas) DrawText(x, y float64, text string, opt *TextOption) *jsCanva
|
||||
}
|
||||
|
||||
// 识别类
|
||||
func (j *jsCanvas) DecodeAll() (string, error) {
|
||||
res, err := j.c.DecodeAll()
|
||||
if err != nil {
|
||||
return "", jsmod.MakeError(err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
func (j *jsCanvas) Recognize() (string, error) {
|
||||
res, err := j.c.Recognize()
|
||||
if err != nil {
|
||||
return "", jsmod.MakeError(err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
func (j *jsCanvas) DecodeQRCode() (string, error) {
|
||||
res, err := j.c.DecodeQRCode()
|
||||
if err != nil {
|
||||
return "", jsmod.MakeError(err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
func (j *jsCanvas) DecodeAll() (string, error) { return j.c.DecodeAll() }
|
||||
func (j *jsCanvas) Recognize() (string, error) { return j.c.Recognize() }
|
||||
func (j *jsCanvas) DecodeQRCode() (string, error) { return j.c.DecodeQRCode() }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user