You've already forked VptPassiveAdapter
- 修改 config.yaml 中的 API 地址、存储配置及设备信息 - 新增 Viid 配置项支持新功能模块 - 在 go.mod 和 go.sum 中更新依赖包版本,引入新的第三方库 - 添加 model/custom_time.go 文件实现自定义时间类型的 JSON 序列化与反序列化 - 调整 DTO 结构体以适配新的配置项 - 升级 OTLP 导出器相关依赖并移除旧的标准输出导出器 - 引入 Gin 框架及相关中间件提升服务性能 - 更新 Protobuf 和 gRPC 相关依赖至最新版本 - 增加 zap 日志库和 lumberjack 日志轮转支持 - 添加 sonic、json-iterator 等高性能 JSON 处理库优化数据解析效率 - 引入 testify 断言库增强测试代码可读性 - 更新 sync 包版本提高并发安全性 - 添加 mock 工具支持单元测试模拟对象 - 引入 validator/v10 实现请求参数校验功能 - 更新 crypto、net、text 等标准库依赖确保安全性和兼容性 - 增加 mimetype 库用于文件类型识别 - 引入 quic-go 支持 HTTP/3 协议通信 - 添加 base64x
97 lines
2.3 KiB
Go
97 lines
2.3 KiB
Go
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/draw"
|
|
"image/jpeg"
|
|
_ "image/png" // Register PNG decoder just in case
|
|
)
|
|
|
|
// GenerateThumbnail creates a thumbnail by cropping the original image.
|
|
// The thumbnail size is 1/2 of the original dimensions.
|
|
// The crop is centered on the provided coordinates (centerX, centerY), constrained to image bounds.
|
|
func GenerateThumbnail(srcData []byte, centerX, centerY int) ([]byte, error) {
|
|
// 1. Decode image
|
|
img, _, err := image.Decode(bytes.NewReader(srcData))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode image failed: %v", err)
|
|
}
|
|
|
|
bounds := img.Bounds()
|
|
origW := bounds.Dx()
|
|
origH := bounds.Dy()
|
|
|
|
// 2. Calculate Target Dimensions (1/2 of original)
|
|
targetW := origW / 2
|
|
targetH := origH / 2
|
|
|
|
if targetW == 0 || targetH == 0 {
|
|
return nil, fmt.Errorf("image too small to generate half-size thumbnail")
|
|
}
|
|
|
|
// 3. Calculate Crop Origin (Top-Left)
|
|
// cropX = centerX - targetW / 2
|
|
cropX := centerX - targetW/2
|
|
cropY := centerY - targetH/2
|
|
|
|
// 4. Constrain to Bounds
|
|
if cropX < 0 {
|
|
cropX = 0
|
|
}
|
|
if cropY < 0 {
|
|
cropY = 0
|
|
}
|
|
if cropX+targetW > origW {
|
|
cropX = origW - targetW
|
|
}
|
|
if cropY+targetH > origH {
|
|
cropY = origH - targetH
|
|
}
|
|
|
|
// Double check after adjustment
|
|
if cropX < 0 {
|
|
cropX = 0
|
|
}
|
|
if cropY < 0 {
|
|
cropY = 0
|
|
}
|
|
|
|
// 5. Perform Crop
|
|
cropRect := image.Rect(cropX, cropY, cropX+targetW, cropY+targetH)
|
|
|
|
var dst image.Image
|
|
|
|
// Try to use SubImage if supported for performance
|
|
type SubImager interface {
|
|
SubImage(r image.Rectangle) image.Image
|
|
}
|
|
|
|
if sub, ok := img.(SubImager); ok {
|
|
dst = sub.SubImage(cropRect)
|
|
} else {
|
|
// Fallback: create new image and draw
|
|
rgba := image.NewRGBA(image.Rect(0, 0, targetW, targetH))
|
|
draw.Draw(rgba, rgba.Bounds(), img, cropRect.Min, draw.Src)
|
|
dst = rgba
|
|
}
|
|
|
|
// 6. Encode to JPEG
|
|
var buf bytes.Buffer
|
|
err = jpeg.Encode(&buf, dst, &jpeg.Options{Quality: 85})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encode thumbnail failed: %v", err)
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// Helper to process using LeftTop and RightBottom coordinates
|
|
func GenerateThumbnailFromCoords(srcData []byte, ltX, ltY, rbX, rbY int) ([]byte, error) {
|
|
// Calculate center based on the provided coordinates
|
|
centerX := (ltX + rbX) / 2
|
|
centerY := (ltY + rbY) / 2
|
|
return GenerateThumbnail(srcData, centerX, centerY)
|
|
}
|