Files
go-pixelnebula/pixelnebula.go
2025-04-19 13:30:14 +08:00

1470 lines
39 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package pixelnebula
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/landaiqing/go-pixelnebula/animation"
"github.com/landaiqing/go-pixelnebula/cache"
"github.com/landaiqing/go-pixelnebula/converter"
"github.com/landaiqing/go-pixelnebula/errors"
"github.com/landaiqing/go-pixelnebula/style"
"github.com/landaiqing/go-pixelnebula/theme"
)
const (
hashLength = 12
)
var (
// 优化正则表达式,使用更高效的模式
numberRegex = regexp.MustCompile(`[0-9]`)
// 使用非贪婪模式并优化颜色匹配模式
colorRegex = regexp.MustCompile(`#([^;]*);`)
// 使用字节池减少内存分配
hashBufPool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 64)
return &buf
},
}
// 使用分片锁减少锁竞争
keyCacheShards = 16 // 分片数量
keyCacheLocks = make([]sync.RWMutex, keyCacheShards)
keyCacheShardData = make([]map[string][2]int, keyCacheShards)
// 使用对象池减少内存分配
builderPool = sync.Pool{
New: func() interface{} {
return &strings.Builder{}
},
}
mapPool = sync.Pool{
New: func() interface{} {
return make(map[string]string, 6)
},
}
keyMapPool = sync.Pool{
New: func() interface{} {
return make(map[string][2]int, 6)
},
}
)
// init 初始化分片缓存
func init() {
// 初始化分片缓存
for i := 0; i < keyCacheShards; i++ {
keyCacheShardData[i] = make(map[string][2]int)
}
}
// 计算字符串哈希获取分片索引
func getShardIndex(key string) int {
var hashKey uint32
for i := 0; i < len(key); i++ {
hashKey = hashKey*31 + uint32(key[i])
}
return int(hashKey % uint32(keyCacheShards))
}
type PNOptions struct {
ThemeIndex int // 主题索引
StyleIndex int // 风格索引
ParallelRender bool // 是否启用并行渲染
ConcurrencyPool int // 并发池大小默认为CPU核心数
}
type PixelNebula struct {
SvgEnd string
ThemeManager *theme.Manager
StyleManager *style.Manager
AnimManager *animation.Manager
Cache *cache.PNCache
Hasher hash.Hash
Options *PNOptions
Width int
Height int
ImgData []byte
}
// NewPixelNebula 创建一个PixelNebula实例
func NewPixelNebula() *PixelNebula {
return &PixelNebula{
SvgEnd: "</svg>",
ThemeManager: theme.NewThemeManager(),
StyleManager: style.NewShapeManager(),
AnimManager: animation.NewAnimationManager(),
Hasher: sha256.New(),
Options: &PNOptions{ThemeIndex: -1, StyleIndex: -1, ParallelRender: false, ConcurrencyPool: runtime.NumCPU()}, // 初始化为 -1 表示未设置
Width: 231,
Height: 231,
}
}
// getSvgStart 根据当前宽高生成SVG开始标签
func (pn *PixelNebula) getSvgStart() string {
return fmt.Sprintf("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 %d %d\">", pn.Width, pn.Height)
}
// WithTheme 设置固定主题
func (pn *PixelNebula) WithTheme(themeIndex int) *PixelNebula {
// 如果已设置 style,则验证主题索引是否有效
if styleIndex := pn.Options.StyleIndex; styleIndex >= 0 {
// 获取该风格下的主题数量
themeCount := pn.ThemeManager.ThemeCount(styleIndex)
if themeIndex < 0 || themeIndex >= themeCount {
log.Printf("pixelnebula: theme index range is:[0, %d), but got %d", themeCount, themeIndex)
panic(errors.ErrInvalidTheme)
}
}
pn.Options.ThemeIndex = themeIndex
return pn
}
// WithStyle 设置固定风格
func (pn *PixelNebula) WithStyle(style style.StyleType) *PixelNebula {
styleIndex, err := pn.StyleManager.GetStyleIndex(style)
if err != nil {
panic(err)
}
pn.Options.StyleIndex = styleIndex
return pn
}
// WithSize 设置尺寸
func (pn *PixelNebula) WithSize(width, height int) *PixelNebula {
pn.Width = width
pn.Height = height
return pn
}
// WithCustomizeTheme 设置自定义主题
func (pn *PixelNebula) WithCustomizeTheme(theme []theme.Theme) *PixelNebula {
pn.ThemeManager.CustomizeTheme(theme)
return pn
}
// WithCustomizeStyle 设置自定义风格
func (pn *PixelNebula) WithCustomizeStyle(style []style.StyleSet) *PixelNebula {
pn.StyleManager.CustomizeStyle(style)
return pn
}
// hashToNum 将哈希字符串转换为数字
func (pn *PixelNebula) hashToNum(hash []string) int64 {
if len(hash) == 0 {
return 0
}
// 使用位运算直接计算哈希值,减少内存分配和计算复杂度
var result int64
for _, h := range hash {
// 优化直接处理字符避免ParseInt的开销
for i := 0; i < len(h); i++ {
if h[i] >= '0' && h[i] <= '9' {
// 使用位运算进行计算: result = result*10 + (h[i] - '0')
result = (result << 3) + (result << 1) + int64(h[i]-'0')
}
}
}
// 确保结果为正数
if result < 0 {
result = -result
}
return result
}
// getCacheKey 生成缓存键的哈希表示
func (pn *PixelNebula) getCacheKey(id string, hash []string, index int, opts *PNOptions) string {
// 构造一个唯一的键字符串
var key string
if opts != nil && opts.StyleIndex >= 0 && opts.ThemeIndex >= 0 {
key = fmt.Sprintf("%s_%d_%d_%d", id, index, opts.StyleIndex, opts.ThemeIndex)
} else if len(hash) > 0 {
key = fmt.Sprintf("%s_%d_%s", id, index, strings.Join(hash, ""))
} else {
key = fmt.Sprintf("%s_%d", id, index)
}
return key
}
// calcKey 计算主题和部分的键值
func (pn *PixelNebula) calcKey(hash []string, opts *PNOptions) [2]int {
// 检查是否使用固定值
if opts != nil && opts.StyleIndex >= 0 && opts.ThemeIndex >= 0 {
return [2]int{opts.StyleIndex, opts.ThemeIndex}
}
// 计算缓存键
cacheKey := strings.Join(hash, "")
// 计算分片索引
shardIndex := getShardIndex(cacheKey)
// 尝试从缓存中获取结果,使用读锁
keyCacheLocks[shardIndex].RLock()
if result, ok := keyCacheShardData[shardIndex][cacheKey]; ok {
keyCacheLocks[shardIndex].RUnlock()
return result
}
keyCacheLocks[shardIndex].RUnlock()
// 计算哈希值
hashNum := pn.hashToNum(hash)
// 获取可用的风格数量
styleCount := pn.ThemeManager.StyleCount()
if styleCount == 0 {
return [2]int{0, 0}
}
// 使用位运算优化取模操作
styleIndex := int(hashNum % int64(styleCount))
if styleIndex < 0 {
styleIndex = -styleIndex
}
// 获取该风格下的主题数量
themeCount := pn.ThemeManager.ThemeCount(styleIndex)
if themeCount == 0 {
return [2]int{styleIndex, 0}
}
// 使用哈希值的不同部分计算主题索引
themeIndex := int(hashNum % int64(themeCount))
if themeIndex < 0 {
themeIndex = -themeIndex
}
// 将结果存入缓存,使用写锁
result := [2]int{styleIndex, themeIndex}
keyCacheLocks[shardIndex].Lock()
keyCacheShardData[shardIndex][cacheKey] = result
keyCacheLocks[shardIndex].Unlock()
return result
}
// WithCache 设置缓存选项
func (pn *PixelNebula) WithCache(options cache.CacheOptions) *PixelNebula {
pn.Cache = cache.NewCache(options)
// 确保启动监控器
if pn.Cache != nil && options.Monitoring.Enabled && pn.Cache.GetMonitor() == nil {
pn.Cache.Monitor = cache.NewMonitor(pn.Cache, options.Monitoring)
pn.Cache.Monitor.Start()
}
return pn
}
// WithDefaultCache 设置默认缓存选项
func (pn *PixelNebula) WithDefaultCache() *PixelNebula {
pn.Cache = cache.NewDefaultCache()
// 确保启动监控器
if pn.Cache != nil && pn.Cache.GetOptions().Monitoring.Enabled && pn.Cache.GetMonitor() == nil {
pn.Cache.Monitor = cache.NewMonitor(pn.Cache, pn.Cache.GetOptions().Monitoring)
pn.Cache.Monitor.Start()
}
return pn
}
// WithCompression 设置压缩选项
func (pn *PixelNebula) WithCompression(options cache.CompressOptions) *PixelNebula {
if pn.Cache != nil {
cacheOptions := pn.Cache.GetOptions()
cacheOptions.Compression = options
pn.Cache.UpdateOptions(cacheOptions)
}
return pn
}
// WithMonitoring 设置监控选项
func (pn *PixelNebula) WithMonitoring(options cache.MonitorOptions) *PixelNebula {
if pn.Cache != nil {
cacheOptions := pn.Cache.GetOptions()
cacheOptions.Monitoring = options
pn.Cache.UpdateOptions(cacheOptions)
// 如果启用了监控但监控器尚未创建,则创建并启动监控器
if options.Enabled && pn.Cache.Monitor == nil {
pn.Cache.Monitor = cache.NewMonitor(pn.Cache, options)
pn.Cache.Monitor.Start()
}
}
return pn
}
// WithAnimation 添加动画效果
func (pn *PixelNebula) WithAnimation(animation animation.Animation) *PixelNebula {
pn.AnimManager.AddAnimation(animation)
return pn
}
// WithRotateAnimation 添加旋转动画
func (pn *PixelNebula) WithRotateAnimation(targetID string, fromAngle, toAngle float64, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewRotateAnimation(targetID, fromAngle, toAngle, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithGradientAnimation 添加渐变动画
func (pn *PixelNebula) WithGradientAnimation(targetID string, colors []string, duration float64, repeatCount int, animate bool) *PixelNebula {
anim := animation.NewGradientAnimation(targetID, colors, duration, repeatCount, animate)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithTransformAnimation 添加变换动画
func (pn *PixelNebula) WithTransformAnimation(targetID string, transformType string, from, to string, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewTransformAnimation(targetID, transformType, from, to, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithFadeAnimation 添加淡入淡出动画
func (pn *PixelNebula) WithFadeAnimation(targetID string, from, to string, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewFadeAnimation(targetID, from, to, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithPathAnimation 添加路径动画
func (pn *PixelNebula) WithPathAnimation(targetID string, path string, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewPathAnimation(targetID, path, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithPathAnimationRotate 添加带旋转的路径动画
func (pn *PixelNebula) WithPathAnimationRotate(targetID string, path string, rotate string, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewPathAnimation(targetID, path, duration, repeatCount)
anim.WithRotate(rotate)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithColorAnimation 添加颜色变换动画
func (pn *PixelNebula) WithColorAnimation(targetID string, property string, fromColor, toColor string, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewColorAnimation(targetID, property, fromColor, toColor, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithBounceAnimation 添加弹跳动画
func (pn *PixelNebula) WithBounceAnimation(targetID string, property string, from, to string, bounceCount int, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewBounceAnimation(targetID, property, from, to, bounceCount, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithWaveAnimation 添加波浪动画
func (pn *PixelNebula) WithWaveAnimation(targetID string, amplitude, frequency float64, direction string, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewWaveAnimation(targetID, amplitude, frequency, direction, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithBlinkAnimation 添加闪烁动画
func (pn *PixelNebula) WithBlinkAnimation(targetID string, minOpacity, maxOpacity float64, blinkCount int, duration float64, repeatCount int) *PixelNebula {
anim := animation.NewBlinkAnimation(targetID, minOpacity, maxOpacity, blinkCount, duration, repeatCount)
pn.AnimManager.AddAnimation(anim)
return pn
}
// WithParallelRender 启用并行渲染
func (pn *PixelNebula) WithParallelRender(enabled bool) *PixelNebula {
pn.Options.ParallelRender = enabled
return pn
}
// WithConcurrencyPool 设置并发池大小
func (pn *PixelNebula) WithConcurrencyPool(size int) *PixelNebula {
if size <= 0 {
size = runtime.NumCPU()
}
pn.Options.ConcurrencyPool = size
return pn
}
// SVGBuilder 用于处理SVG生成后的链式操作
type SVGBuilder struct {
pn *PixelNebula
svg string
id string
sansEnv bool
themeIndex int
styleIndex int
width int
height int
hasError error
}
// Generate 现在返回 SVGBuilder
func (pn *PixelNebula) Generate(id string, sansEnv bool) *SVGBuilder {
return &SVGBuilder{
pn: pn,
id: id,
sansEnv: sansEnv,
width: pn.Width,
height: pn.Height,
themeIndex: pn.Options.ThemeIndex,
styleIndex: pn.Options.StyleIndex,
}
}
// SetTheme 设置主题
func (sb *SVGBuilder) SetTheme(theme int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
themeCount := sb.pn.ThemeManager.ThemeCount(sb.styleIndex)
if theme < 0 || theme >= themeCount {
log.Printf("pixelnebula: theme index range is:[0, %d), but got %d", themeCount, theme)
sb.hasError = errors.ErrInvalidTheme
return sb
}
sb.themeIndex = theme
return sb
}
// SetStyle 设置风格
// 注意当使用WithCustomizeStyle设置自定义风格后此方法将无法正常工作应使用SetStyleByIndex代替
func (sb *SVGBuilder) SetStyle(style style.StyleType) *SVGBuilder {
if sb.hasError != nil {
return sb
}
index, err := sb.pn.StyleManager.GetStyleIndex(style)
if err != nil {
sb.hasError = err
return sb
}
sb.styleIndex = index
return sb
}
// SetStyleByIndex 设置风格索引
// 此方法可用于设置自定义风格的索引特别是在使用WithCustomizeStyle后
func (sb *SVGBuilder) SetStyleByIndex(index int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
themeCount := sb.pn.ThemeManager.StyleCount()
if index < 0 || index >= themeCount {
log.Printf("pixelnebula: style index range is:[0, %d), but got %d", themeCount, index)
sb.hasError = errors.ErrInvalidStyleName
return sb
}
sb.styleIndex = index
return sb
}
// SetSize 设置尺寸
func (sb *SVGBuilder) SetSize(width, height int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
sb.width = width
sb.height = height
return sb
}
// SetAnimation 添加动画效果
func (sb *SVGBuilder) SetAnimation(anim animation.Animation) *SVGBuilder {
if sb.hasError != nil {
return sb
}
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetRotateAnimation 添加旋转动画
func (sb *SVGBuilder) SetRotateAnimation(targetID string, fromAngle, toAngle float64, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewRotateAnimation(targetID, fromAngle, toAngle, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetGradientAnimation 添加渐变动画
func (sb *SVGBuilder) SetGradientAnimation(targetID string, colors []string, duration float64, repeatCount int, animate bool) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewGradientAnimation(targetID, colors, duration, repeatCount, animate)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetTransformAnimation 添加变换动画
func (sb *SVGBuilder) SetTransformAnimation(targetID string, transformType string, from, to string, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewTransformAnimation(targetID, transformType, from, to, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetFadeAnimation 添加淡入淡出动画
func (sb *SVGBuilder) SetFadeAnimation(targetID string, from, to string, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewFadeAnimation(targetID, from, to, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetPathAnimation 添加路径动画
func (sb *SVGBuilder) SetPathAnimation(targetID string, path string, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewPathAnimation(targetID, path, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetPathAnimationRotate 添加带旋转的路径动画
func (sb *SVGBuilder) SetPathAnimationRotate(targetID string, path string, rotate string, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewPathAnimation(targetID, path, duration, repeatCount)
anim.WithRotate(rotate)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetColorAnimation 添加颜色变换动画
func (sb *SVGBuilder) SetColorAnimation(targetID string, property string, fromColor, toColor string, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewColorAnimation(targetID, property, fromColor, toColor, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetBounceAnimation 添加弹跳动画
func (sb *SVGBuilder) SetBounceAnimation(targetID string, property string, from, to string, bounceCount int, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewBounceAnimation(targetID, property, from, to, bounceCount, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetWaveAnimation 添加波浪动画
func (sb *SVGBuilder) SetWaveAnimation(targetID string, amplitude, frequency float64, direction string, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewWaveAnimation(targetID, amplitude, frequency, direction, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetBlinkAnimation 添加闪烁动画
func (sb *SVGBuilder) SetBlinkAnimation(targetID string, minOpacity, maxOpacity float64, blinkCount int, duration float64, repeatCount int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
anim := animation.NewBlinkAnimation(targetID, minOpacity, maxOpacity, blinkCount, duration, repeatCount)
sb.pn.AnimManager.AddAnimation(anim)
return sb
}
// SetParallelRender 设置是否启用并行渲染
func (sb *SVGBuilder) SetParallelRender(enabled bool) *SVGBuilder {
if sb.hasError != nil {
return sb
}
sb.pn.Options.ParallelRender = enabled
return sb
}
// SetConcurrencyPool 设置并发池大小
func (sb *SVGBuilder) SetConcurrencyPool(size int) *SVGBuilder {
if sb.hasError != nil {
return sb
}
if size <= 0 {
size = runtime.NumCPU()
}
sb.pn.Options.ConcurrencyPool = size
return sb
}
// Build 生成最终的SVG
func (sb *SVGBuilder) Build() *SVGBuilder {
if sb.hasError != nil {
return sb
}
opts := &PNOptions{
ThemeIndex: sb.themeIndex,
StyleIndex: sb.styleIndex,
}
svg, err := sb.pn.generateSVG(sb.id, sb.sansEnv, opts)
if err != nil {
sb.hasError = err
return sb
}
sb.svg = svg
sb.pn.ImgData = []byte(svg)
sb.pn.Width = sb.width
sb.pn.Height = sb.height
return sb
}
// ToSVG 获取SVG字符串
func (sb *SVGBuilder) ToSVG() (string, error) {
if sb.svg == "" {
sb = sb.Build()
}
if sb.hasError != nil {
return "", sb.hasError
}
return sb.svg, nil
}
// ToBase64 获取Base64编码的SVG字符串 注意:这个设置宽高无效
func (sb *SVGBuilder) ToBase64() (string, error) {
if sb.svg == "" {
sb = sb.Build()
}
if sb.hasError != nil {
return "", sb.hasError
}
conv := converter.NewSVGConverter([]byte(sb.svg), sb.width, sb.height)
return conv.ToBase64()
}
// ToFile 将SVG代码保存到文件
func (sb *SVGBuilder) ToFile(filePath string) error {
if sb.svg == "" {
sb = sb.Build()
}
if sb.hasError != nil {
return sb.hasError
}
return os.WriteFile(filePath, []byte(sb.svg), 0644)
}
// 将原来的 GenerateSVG 重命名为 generateSVG作为内部方法
func (pn *PixelNebula) generateSVG(id string, sansEnv bool, opts *PNOptions) (svg string, err error) {
if opts == nil {
opts = pn.Options
}
// 验证参数
if id == "" {
return "", errors.ErrAvatarIDRequired
}
// 如果启用了缓存,先尝试从缓存获取
if pn.Cache != nil {
cacheKey := cache.CacheKey{
Id: id,
SansEnv: sansEnv,
}
if opts != nil {
cacheKey.Theme = opts.ThemeIndex
cacheKey.Part = opts.StyleIndex
}
if cachedSVG, found := pn.Cache.Get(cacheKey); found {
return cachedSVG, nil
}
}
// 使用对象池获取缓冲区
hashBuf := hashBufPool.Get().(*[]byte)
defer hashBufPool.Put(hashBuf)
// 计算avatarId的哈希值 - 优化版本
pn.Hasher.Reset()
pn.Hasher.Write([]byte(id))
sum := pn.Hasher.Sum((*hashBuf)[:0])
s := hex.EncodeToString(sum)
hashStr := numberRegex.FindAllString(s, -1)
if len(hashStr) < hashLength {
return "", errors.ErrInsufficientHash
}
hashStr = hashStr[0:hashLength]
// 从对象池获取映射
p := keyMapPool.Get().(map[string][2]int)
defer func() {
// 清空并归还对象池
for k := range p {
delete(p, k)
}
keyMapPool.Put(p)
}()
// 计算各部分的键值
p[string(style.TypeEnv)] = pn.calcKey(hashStr[:2], opts)
p[string(style.TypeClo)] = pn.calcKey(hashStr[2:4], opts)
p[string(style.TypeHead)] = pn.calcKey(hashStr[4:6], opts)
p[string(style.TypeMouth)] = pn.calcKey(hashStr[6:8], opts)
p[string(style.TypeEyes)] = pn.calcKey(hashStr[8:10], opts)
p[string(style.TypeTop)] = pn.calcKey(hashStr[10:], opts)
// 获取结果映射
final := mapPool.Get().(map[string]string)
defer func() {
// 清空并归还对象池
for k := range final {
delete(final, k)
}
mapPool.Put(final)
}()
// 根据是否启用并行渲染选择处理方式
if opts.ParallelRender {
// 并行处理
var wg sync.WaitGroup
errChan := make(chan error, len(p))
// 创建互斥锁来保护 final map
var finalMux sync.Mutex
// 对每个部分启动一个 goroutine
for k, v := range p {
wg.Add(1)
go func(key string, val [2]int) {
defer wg.Done()
// 使用临时变量处理这个部分
tempResult := ""
// 获取主题颜色
themePart, err := pn.ThemeManager.GetTheme(val[0], val[1])
if err != nil {
errChan <- err
return
}
colors, ok := themePart[key]
if !ok {
errChan <- errors.ErrInvalidColor
return
}
// 获取形状SVG
shapeType := style.ShapeType(key)
svgPart, err := pn.StyleManager.GetShape(val[0], shapeType)
if err != nil {
errChan <- err
return
}
match := colorRegex.FindAllStringSubmatch(svgPart, -1)
// 从对象池获取Builder
sb := builderPool.Get().(*strings.Builder)
sb.Reset()
sb.Grow(len(svgPart) + 50) // 预分配足够的容量
lastIndex := 0
for i, m := range match {
if i < len(colors) {
// 找到完整匹配的位置
index := strings.Index(svgPart[lastIndex:], m[0]) + lastIndex
// 添加匹配前的部分
sb.WriteString(svgPart[lastIndex:index])
// 添加替换后的颜色
// 检查颜色值是否已经包含#前缀
if strings.HasPrefix(colors[i], "#") {
sb.WriteString(colors[i])
} else {
sb.WriteString("#")
sb.WriteString(colors[i])
}
sb.WriteString(";")
// 更新lastIndex
lastIndex = index + len(m[0])
}
}
// 添加剩余部分
sb.WriteString(svgPart[lastIndex:])
tempResult = sb.String()
// 归还Builder到对象池
builderPool.Put(sb)
// 使用互斥锁保护对 final map 的写入
finalMux.Lock()
final[key] = tempResult
finalMux.Unlock()
}(k, v)
}
// 等待所有部分处理完成
wg.Wait()
// 检查是否有错误
select {
case err := <-errChan:
return "", err
default:
// 没有错误,继续处理
}
} else {
// 串行处理
for k, v := range p {
if err := pn.processSVGPart(k, v, final); err != nil {
return "", err
}
}
}
// 使用对象池获取主Builder来构建最终SVG
builder := builderPool.Get().(*strings.Builder)
builder.Reset()
// 预估SVG大小避免多次内存分配
builder.Grow(2048) // 2KB 应该足够容纳大多数SVG
// 添加SVG开始标签
builder.WriteString(pn.getSvgStart())
// 获取动画定义
animations := pn.AnimManager.GenerateSVGAnimations()
if animations != "" {
builder.WriteString(animations)
}
// 构建和处理旋转动画 - 使用对象池
rotateAnimations := make(map[string]bool)
rotateAnimationSVGs := make(map[string]string)
// 收集旋转动画
for _, anim := range pn.AnimManager.GetAnimations() {
if rotateAnim, ok := anim.(*animation.RotateAnimation); ok {
targetID := anim.GetTargetID()
rotateAnimations[targetID] = true
// 提取animateTransform部分
svgCode := rotateAnim.GenerateSVG()
if start := strings.Index(svgCode, "<animateTransform"); start != -1 {
if end := strings.Index(svgCode[start:], "/>"); end != -1 {
rotateAnimationSVGs[targetID] = svgCode[start : start+end+2]
}
}
}
}
// 处理环境
if !sansEnv {
if _, hasRotate := rotateAnimations["env"]; hasRotate {
builder.WriteString("<g style=\"transform-box: fill-box; transform-origin: center;\">\n")
builder.WriteString(final["env"])
if animSVG, ok := rotateAnimationSVGs["env"]; ok {
builder.WriteString(animSVG)
}
builder.WriteString("</g>\n")
} else {
builder.WriteString(final["env"])
}
}
// 处理其他元素
elements := []string{"head", "clo", "top", "eyes", "mouth"}
for _, elem := range elements {
if _, hasRotate := rotateAnimations[elem]; hasRotate {
builder.WriteString("<g style=\"transform-box: fill-box; transform-origin: center;\">\n")
builder.WriteString(final[elem])
if animSVG, ok := rotateAnimationSVGs[elem]; ok {
builder.WriteString(animSVG)
}
builder.WriteString("</g>\n")
} else {
builder.WriteString(final[elem])
}
}
builder.WriteString(pn.SvgEnd)
svg = builder.String()
// 将生成的SVG存储到实例中
pn.ImgData = []byte(svg)
// 归还Builder到对象池
builderPool.Put(builder)
// 如果启用了缓存,将结果存入缓存
if pn.Cache != nil {
cacheKey := cache.CacheKey{
Id: id,
SansEnv: sansEnv,
}
if opts != nil {
cacheKey.Theme = opts.ThemeIndex
cacheKey.Part = opts.StyleIndex
}
pn.Cache.Set(cacheKey, svg)
}
return svg, nil
}
// GetCacheStats 获取缓存统计信息
func (pn *PixelNebula) GetCacheStats() (size, hits, misses int, hitRate float64, enabled bool, maxSize int, expiration time.Duration, evictionType string) {
if pn.Cache == nil {
log.Println("pixelnebula: 缓存未初始化请先调用WithCache或WithDefaultCache")
return 0, 0, 0, 0, false, 0, 0, ""
}
hits, misses, hitRate = pn.Cache.Stats()
options := pn.Cache.GetOptions()
return pn.Cache.Size(), hits, misses, hitRate, options.Enabled, options.Size, options.Expiration, options.EvictionType
}
// CacheItemInfo 缓存项信息结构体
type CacheItemInfo struct {
Key cache.CacheKey
SVG string
Compressed []byte
IsCompressed bool
CreatedAt time.Time
LastUsed time.Time
}
// GetCacheItems 获取所有缓存项
func (pn *PixelNebula) GetCacheItems() []CacheItemInfo {
var result []CacheItemInfo
if pn.Cache == nil {
log.Println("pixelnebula: 缓存未初始化请先调用WithCache或WithDefaultCache")
return result
}
// 获取内部缓存项
items := pn.Cache.GetAllItems()
if len(items) == 0 {
log.Println("pixelnebula: 缓存中没有数据请先生成一些SVG")
}
for key, item := range items {
cacheItem := CacheItemInfo{
Key: key,
SVG: item.SVG,
Compressed: item.Compressed,
IsCompressed: item.IsCompressed,
CreatedAt: item.CreatedAt,
LastUsed: item.LastUsed,
}
result = append(result, cacheItem)
}
return result
}
// MonitorSampleInfo 监控样本信息
type MonitorSampleInfo struct {
Timestamp time.Time
Size int
Hits int
Misses int
HitRate float64
MemoryUsage int64
}
// GetMonitorStats 获取监控统计信息
func (pn *PixelNebula) GetMonitorStats() (enabled bool, sampleInterval, adjustInterval time.Duration,
targetHitRate float64, lastAdjusted time.Time, samples []MonitorSampleInfo) {
if pn.Cache == nil {
log.Println("pixelnebula: 缓存未初始化请先调用WithCache或WithDefaultCache")
return false, 0, 0, 0, time.Time{}, nil
}
options := pn.Cache.GetOptions().Monitoring
// 确保监控器已启用并初始化
if !options.Enabled {
log.Println("pixelnebula: 监控未启用请设置Monitoring.Enabled=true")
return options.Enabled, options.SampleInterval, options.AdjustInterval, options.TargetHitRate, time.Time{}, nil
}
if pn.Cache.GetMonitor() == nil {
log.Println("pixelnebula: 监控器未初始化,正在初始化...")
pn.Cache.Monitor = cache.NewMonitor(pn.Cache, options)
pn.Cache.Monitor.Start()
}
monitor := pn.Cache.GetMonitor()
stats := monitor.GetStats()
sampleHistory := monitor.GetSampleHistory()
if len(sampleHistory) == 0 {
log.Println("pixelnebula: 监控样本为空,请等待采样完成")
}
// 转换样本历史
samplesInfo := make([]MonitorSampleInfo, 0, len(sampleHistory))
for _, sample := range sampleHistory {
sampleInfo := MonitorSampleInfo{
Timestamp: sample.LastAdjusted,
Size: sample.Size,
Hits: sample.Hits,
Misses: sample.Misses,
HitRate: sample.HitRate,
MemoryUsage: sample.MemoryUsage,
}
samplesInfo = append(samplesInfo, sampleInfo)
}
return options.Enabled, options.SampleInterval, options.AdjustInterval,
options.TargetHitRate, stats.LastAdjusted, samplesInfo
}
// DeleteCacheItem 删除指定的缓存项
func (pn *PixelNebula) DeleteCacheItem(key string) bool {
if pn.Cache == nil {
log.Println("pixelnebula: 缓存未初始化请先调用WithCache或WithDefaultCache")
return false
}
// 解析key字符串格式为"id_sansEnv_theme_part"
parts := strings.Split(key, "_")
if len(parts) < 4 {
log.Printf("pixelnebula: 无效的key格式: %s应为'id_sansEnv_theme_part'", key)
return false
}
id := parts[0]
sansEnv := parts[1] == "true"
themeItem, err := strconv.Atoi(parts[2])
if err != nil {
log.Printf("pixelnebula: 解析theme失败: %v", err)
return false
}
part, err := strconv.Atoi(parts[3])
if err != nil {
log.Printf("pixelnebula: 解析part失败: %v", err)
return false
}
// 构造CacheKey
cacheKey := cache.CacheKey{
Id: id,
SansEnv: sansEnv,
Theme: themeItem,
Part: part,
}
result := pn.Cache.DeleteItem(cacheKey)
if !result {
log.Printf("pixelnebula: 未找到缓存项: %s", key)
}
return result
}
// ClearCache 清空缓存
func (pn *PixelNebula) ClearCache() {
if pn.Cache == nil {
log.Println("pixelnebula: 缓存未初始化请先调用WithCache或WithDefaultCache")
return
}
pn.Cache.Clear()
log.Println("pixelnebula: 缓存已清空")
}
// 将原来的 generateSVG 方法中的部分代码提取为独立函数,方便并行处理
func (pn *PixelNebula) processSVGPart(k string, v [2]int, final map[string]string) error {
// 获取主题颜色
themePart, err := pn.ThemeManager.GetTheme(v[0], v[1])
if err != nil {
return err
}
colors, ok := themePart[k]
if !ok {
return errors.ErrInvalidColor
}
// 获取形状SVG
shapeType := style.ShapeType(k)
svgPart, err := pn.StyleManager.GetShape(v[0], shapeType)
if err != nil {
return err
}
match := colorRegex.FindAllStringSubmatch(svgPart, -1)
// 从对象池获取Builder
sb := builderPool.Get().(*strings.Builder)
sb.Reset()
sb.Grow(len(svgPart) + 50) // 预分配足够的容量
lastIndex := 0
for i, m := range match {
if i < len(colors) {
// 找到完整匹配的位置
index := strings.Index(svgPart[lastIndex:], m[0]) + lastIndex
// 添加匹配前的部分
sb.WriteString(svgPart[lastIndex:index])
// 添加替换后的颜色
// 检查颜色值是否已经包含#前缀
if strings.HasPrefix(colors[i], "#") {
sb.WriteString(colors[i])
} else {
sb.WriteString("#")
sb.WriteString(colors[i])
}
sb.WriteString(";")
// 更新lastIndex
lastIndex = index + len(m[0])
}
}
// 添加剩余部分
sb.WriteString(svgPart[lastIndex:])
final[k] = sb.String()
// 归还Builder到对象池
builderPool.Put(sb)
return nil
}
// GenerateBatch 批量生成SVG图像
// ids要生成的ID列表
// sansEnv是否不包含环境
// opts生成选项
// 返回SVG映射表id -> svg和错误
func (pn *PixelNebula) GenerateBatch(ids []string, sansEnv bool, opts *PNOptions) (map[string]string, error) {
if opts == nil {
opts = pn.Options
}
// 验证参数
if len(ids) == 0 {
return nil, errors.ErrAvatarIDRequired
}
// 创建结果映射
result := make(map[string]string, len(ids))
// 如果没有启用并发处理,则串行生成
if !opts.ParallelRender {
for _, id := range ids {
svg, err := pn.generateSVG(id, sansEnv, opts)
if err != nil {
return result, err
}
result[id] = svg
}
return result, nil
}
// 并发生成
// 创建一个工作池限制并发数
workerCount := opts.ConcurrencyPool
if workerCount <= 0 {
workerCount = runtime.NumCPU()
}
// 创建任务通道
tasks := make(chan string, len(ids))
for _, id := range ids {
tasks <- id
}
close(tasks)
// 创建结果通道
type resultPair struct {
id string
svg string
err error
}
resultChan := make(chan resultPair, len(ids))
// 启动工作池
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 创建一个新的PixelNebula实例但只复制必要的字段避免资源竞争
// 特别是创建独立的哈希实例
workerPN := &PixelNebula{
SvgEnd: pn.SvgEnd,
ThemeManager: pn.ThemeManager, // 这些管理器是安全的,因为它们的方法是并发安全的或只读的
StyleManager: pn.StyleManager,
AnimManager: pn.AnimManager,
Cache: pn.Cache, // 缓存有自己的锁机制
Hasher: sha256.New(), // 创建新的哈希实例,避免并发访问冲突
Options: opts,
Width: pn.Width,
Height: pn.Height,
}
for id := range tasks {
svg, err := workerPN.generateSVG(id, sansEnv, opts)
resultChan <- resultPair{id, svg, err}
}
}()
}
// 等待所有工作完成
go func() {
wg.Wait()
close(resultChan)
}()
// 收集结果
for r := range resultChan {
if r.err != nil {
return result, r.err
}
result[r.id] = r.svg
}
return result, nil
}
// SaveBatchToFiles 批量保存SVG到文件
// ids要生成的ID列表
// sansEnv是否不包含环境
// opts生成选项
// filePathPattern文件路径模式如 "output/avatar_%s.svg",其中 %s 将被替换为ID
// 返回:成功保存的文件数量和错误
func (pn *PixelNebula) SaveBatchToFiles(ids []string, sansEnv bool, opts *PNOptions, filePathPattern string) (int, error) {
if opts == nil {
opts = pn.Options
}
// 验证参数
if len(ids) == 0 {
return 0, errors.ErrAvatarIDRequired
}
if filePathPattern == "" {
return 0, fmt.Errorf("文件路径模式不能为空")
}
// 批量生成SVG
svgs, err := pn.GenerateBatch(ids, sansEnv, opts)
if err != nil {
return 0, err
}
// 保存文件的处理函数
saveFile := func(id, svg, pathPattern string) error {
filePath := fmt.Sprintf(pathPattern, id)
// 确保目录存在
dir := filepath.Dir(filePath)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
return os.WriteFile(filePath, []byte(svg), 0644)
}
// 如果不启用并行处理,串行保存
if !opts.ParallelRender {
count := 0
for id, svg := range svgs {
if err := saveFile(id, svg, filePathPattern); err != nil {
return count, err
}
count++
}
return count, nil
}
// 并行保存文件
workerCount := opts.ConcurrencyPool
if workerCount <= 0 {
workerCount = runtime.NumCPU()
}
// 创建任务通道
type saveTask struct {
id string
svg string
}
tasks := make(chan saveTask, len(svgs))
for id, svg := range svgs {
tasks <- saveTask{id, svg}
}
close(tasks)
// 创建结果通道和错误通道
type saveResult struct {
success bool
err error
}
resultChan := make(chan saveResult, len(svgs))
// 启动工作池
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range tasks {
err := saveFile(task.id, task.svg, filePathPattern)
resultChan <- saveResult{err == nil, err}
}
}()
}
// 等待所有工作完成
go func() {
wg.Wait()
close(resultChan)
}()
// 收集结果
successCount := 0
var firstError error
for result := range resultChan {
if result.success {
successCount++
} else if firstError == nil {
// 保存第一个遇到的错误
firstError = result.err
}
}
return successCount, firstError
}
// BatchToBase64 批量转换SVG到Base64
// svgs: SVG字符串映射key为IDvalue为SVG内容
// width, height: SVG尺寸
// 返回: Base64编码的映射表id -> base64和错误
func (pn *PixelNebula) BatchToBase64(svgs map[string]string, width, height int) (map[string]string, error) {
if len(svgs) == 0 {
return nil, fmt.Errorf("没有需要转换的SVG")
}
// 创建结果映射
result := make(map[string]string, len(svgs))
// 如果没有启用并发处理,则串行转换
if !pn.Options.ParallelRender {
for id, svg := range svgs {
conv := converter.NewSVGConverter([]byte(svg), width, height)
base64Str, err := conv.ToBase64()
if err != nil {
return result, err
}
result[id] = base64Str
}
return result, nil
}
// 并发转换
// 创建一个工作池限制并发数
workerCount := pn.Options.ConcurrencyPool
if workerCount <= 0 {
workerCount = runtime.NumCPU()
}
// 创建任务通道
type convTask struct {
id string
svg string
}
tasks := make(chan convTask, len(svgs))
for id, svg := range svgs {
tasks <- convTask{id, svg}
}
close(tasks)
// 创建结果通道
type resultPair struct {
id string
base64 string
err error
}
resultChan := make(chan resultPair, len(svgs))
// 启动工作池
var wg sync.WaitGroup
var resultMutex sync.Mutex // 添加互斥锁保护结果映射
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for task := range tasks {
// 每个任务创建一个新的转换器实例
conv := converter.NewSVGConverter([]byte(task.svg), width, height)
base64Str, err := conv.ToBase64()
// 使用通道传递结果避免直接操作共享map
resultChan <- resultPair{task.id, base64Str, err}
}
}()
}
// 等待所有工作完成
go func() {
wg.Wait()
close(resultChan)
}()
// 收集结果
for r := range resultChan {
if r.err != nil {
return result, r.err
}
// 使用互斥锁保护map写入
resultMutex.Lock()
result[r.id] = r.base64
resultMutex.Unlock()
}
return result, nil
}
// GenerateBatchBase64 添加批量生成Base64方法直接从ID生成
func (pn *PixelNebula) GenerateBatchBase64(ids []string, sansEnv bool, opts *PNOptions, width, height int) (map[string]string, error) {
if opts == nil {
opts = pn.Options
}
// 先批量生成SVG
svgs, err := pn.GenerateBatch(ids, sansEnv, opts)
if err != nil {
return nil, err
}
// 批量转换为Base64
return pn.BatchToBase64(svgs, width, height)
}