package pixelnebula import ( "crypto/sha256" "encoding/hex" "fmt" "hash" "log" "os" "regexp" "strconv" "strings" "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 keyFactor = 0.47 ) var ( // 优化正则表达式,使用更高效的模式 numberRegex = regexp.MustCompile(`[0-9]`) // 使用非贪婪模式并优化颜色匹配模式 colorRegex = regexp.MustCompile(`#([^;]*);`) ) type PNOptions struct { ThemeIndex int // 主题索引, StyleIndex int // 风格索引, } 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: "", themeManager: theme.NewThemeManager(), styleManager: style.NewShapeManager(), animManager: animation.NewAnimationManager(), hasher: sha256.New(), options: &PNOptions{ThemeIndex: -1, StyleIndex: -1}, // 初始化为 -1 表示未设置 width: 231, height: 231, } } // getSvgStart 根据当前宽高生成SVG开始标签 func (pn *PixelNebula) getSvgStart() string { return fmt.Sprintf("", 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 { num, err := strconv.ParseInt(h, 10, 64) if err != nil { continue } // 使用位运算和加法组合多个数字 result = (result << 3) + (result << 1) + num // result * 8 + result * 2 + num } // 确保结果为正数 if result < 0 { result = -result } return result } // 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} } // 直接使用哈希值,不进行 keyFactor 转换 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 } if styleIndex >= styleCount { styleIndex = styleCount - 1 } // 获取该风格下的主题数量 themeCount := pn.themeManager.ThemeCount(styleIndex) if themeCount == 0 { return [2]int{styleIndex, 0} } // 使用哈希值的不同部分计算主题索引 // 使用更简单的计算方式 themeIndex := int(hashNum % int64(themeCount)) if themeIndex < 0 { themeIndex = -themeIndex } if themeIndex >= themeCount { themeIndex = themeCount - 1 } return [2]int{styleIndex, themeIndex} } // WithCache 设置缓存选项 func (pn *PixelNebula) WithCache(options cache.CacheOptions) *PixelNebula { pn.cache = cache.NewCache(options) return pn } // WithDefaultCache 设置默认缓存选项 func (pn *PixelNebula) WithDefaultCache() *PixelNebula { pn.cache = cache.NewDefaultCache() 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 } // 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 } // 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 } } // 计算avatarId的哈希值 pn.hasher.Reset() pn.hasher.Write([]byte(id)) sum := pn.hasher.Sum(nil) s := hex.EncodeToString(sum) hashStr := numberRegex.FindAllString(s, -1) if len(hashStr) < hashLength { return "", errors.ErrInsufficientHash } hashStr = hashStr[0:hashLength] // 预分配map容量以提高性能 var p = make(map[string][2]int, 6) 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) // 预分配map容量 var final = make(map[string]string, 6) for k, v := range p { // 获取主题颜色 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) // 使用strings.Builder提高字符串处理性能 var sb strings.Builder 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() } // 使用strings.Builder构建最终SVG var builder strings.Builder // 预估SVG大小,避免多次内存分配 builder.Grow(1024 * 2) builder.WriteString(pn.getSvgStart()) // 使用动态生成的svgStart // 获取动画定义 animations := pn.animManager.GenerateSVGAnimations() if animations != "" { builder.WriteString(animations) } // 检查是否有旋转动画并获取旋转动画的SVG代码 rotateAnimations := make(map[string]bool) rotateAnimationSVGs := make(map[string]string) for _, anim := range pn.animManager.GetAnimations() { // 检查是否为旋转动画 if rotateAnim, ok := anim.(*animation.RotateAnimation); ok { rotateAnimations[anim.GetTargetID()] = true // 获取旋转动画的SVG代码(只提取animateTransform部分) svgCode := rotateAnim.GenerateSVG() // 提取animateTransform标签 if start := strings.Index(svgCode, ""); end != -1 { rotateAnimationSVGs[anim.GetTargetID()] = svgCode[start : start+end+2] } } } } // 处理元素,如果元素有旋转动画,则包裹在g标签中并添加animateTransform // 只有当不是无环境模式时才添加环境 if !sansEnv { if _, hasRotate := rotateAnimations["env"]; hasRotate { builder.WriteString("\n") builder.WriteString(final["env"]) // 添加animateTransform标签 if animSVG, ok := rotateAnimationSVGs["env"]; ok { builder.WriteString(animSVG) } builder.WriteString("\n") } else { builder.WriteString(final["env"]) } } // 处理其他元素 elements := []string{"head", "clo", "top", "eyes", "mouth"} // 单独处理每个元素 for _, elem := range elements { if _, hasRotate := rotateAnimations[elem]; hasRotate { // 如果元素有旋转动画,则包裹在g标签中 builder.WriteString("\n") builder.WriteString(final[elem]) // 添加animateTransform标签 if animSVG, ok := rotateAnimationSVGs[elem]; ok { // 提取animateTransform标签部分 if start := strings.Index(animSVG, ""); end != -1 { builder.WriteString(animSVG[start : start+end+2]) } } } builder.WriteString("\n") } else { // 如果元素没有旋转动画,直接添加 builder.WriteString(final[elem]) } } builder.WriteString(pn.svgEnd) svg = builder.String() pn.imgData = []byte(svg) // 如果启用了缓存,将结果存入缓存 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 }