🎉 Initial commit
109
.gitignore
Normal file
@@ -0,0 +1,109 @@
|
||||
# ---> Go
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# temporary files, cache and logs
|
||||
tmp/
|
||||
|
||||
# ---> JetBrains
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
73
LICENSE
Normal file
@@ -0,0 +1,73 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||
|
||||
Copyright 2025 landaiqing
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
621
README.md
Normal file
@@ -0,0 +1,621 @@
|
||||
<div style="background-image: linear-gradient(rgba(26, 26, 26, 0.85), rgba(45, 45, 45, 0.85)), url('https://img.picui.cn/free/2025/03/19/67da3b51a5dea.png'); background-size: cover; background-position: center; background-repeat: no-repeat; padding: 40px 20px; border-radius: 15px; margin-bottom: 30px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);">
|
||||
|
||||
# <div align="center" style="display: flex; justify-content: center; align-items: center; margin-bottom: 20px;"><img src="./assets/pixel_planet.gif" height="40"/><span style="font-family: 'Press Start 2P', monospace; background: linear-gradient(45deg, #FF6B6B, #4ECDC4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 2px 2px 4px rgba(0,0,0,0.2);"> PixelNebula </span></div>
|
||||
|
||||
<div align="center" style="margin-bottom: 20px;">
|
||||
<p style="color: #ffffff;"><a href="README_ZH.md" style="color: #4ECDC4;">中文</a> | <strong>English</strong></p>
|
||||
</div>
|
||||
|
||||
<p align="center" style="margin-bottom: 20px;">
|
||||
<img src="./assets/golang_logo.gif" alt="PixelNebula Logo" height="150" style="border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.3);" />
|
||||
</p>
|
||||
|
||||
<div align="center" style="margin-bottom: 20px;">
|
||||
<p style="color: #ffffff; font-size: 1.2em;">
|
||||
<strong>🚀 A powerful, efficient, and customizable SVG avatar generation library for Go</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div align="center" style="margin-bottom: 20px; padding: 15px; border-radius: 10px;">
|
||||
<a href="https://pkg.go.dev/github.com/landaiqing/go-pixelnebula"><img src="https://img.shields.io/badge/go-reference-blue?style=flat-square&logo=go" alt="Go Reference"></a>
|
||||
<a href="https://goreportcard.com/"><img src="https://img.shields.io/badge/go%20report-A+-brightgreen?style=flat-square&logo=go" alt="Go Report Card"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square&logo=github" alt="License"></a>
|
||||
<a href="https://github.com/landaiqing/go-pixelnebula/releases"><img src="https://img.shields.io/badge/version-1.0.0-blue?style=flat-square&logo=github" alt="Version"></a>
|
||||
<a href="http://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square&logo=github" alt="PRs Welcome"></a>
|
||||
</div>
|
||||
|
||||
<p align="center" style="color: #ffffff;background: rgba(255, 255, 255, 0.1);padding: 15px; border-radius: 10px; ">
|
||||
<a href="#-features" style="color: #4ECDC4;">✨ Features</a> •
|
||||
<a href="#-installation" style="color: #4ECDC4;">📦 Installation</a> •
|
||||
<a href="#-quick-start" style="color: #4ECDC4;">🚀 Quick Start</a> •
|
||||
<a href="#-advanced-usage" style="color: #4ECDC4;">🔧 Advanced Usage</a> •
|
||||
<a href="#-animation-effects" style="color: #4ECDC4;">💫 Animation Effects</a> •
|
||||
<a href="#-cache-system" style="color: #4ECDC4;">⚡ Cache System</a> •
|
||||
<a href="#-benchmarks" style="color: #4ECDC4;">📊 Benchmarks</a> •
|
||||
<a href="#-examples-and-demos" style="color: #4ECDC4;">💡 Examples and Demos</a> •
|
||||
<a href="#-contribution-guide" style="color: #4ECDC4;">👥 Contribution Guide</a> •
|
||||
<a href="#-license" style="color: #4ECDC4;">📄 License</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📋 Introduction
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<p><strong style="font-family: 'Press Start 2P', monospace; background: linear-gradient(45deg, #FF6B6B, #4ECDC4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 2px 2px 4px rgba(0,0,0,0.2);">PixelNebula</strong> is a high-performance SVG avatar generation library written in Go, focused on creating beautiful, customizable, and highly animated vector avatars. With PixelNebula, you can easily generate avatars in various styles, add animation effects, and apply different themes and style variations.</p>
|
||||
<p>Whether you're creating user avatars for applications, generating unique identifier icons, or making dynamic visual effects, PixelNebula can meet your needs.</p>
|
||||
</td>
|
||||
<td width="380">
|
||||
<p align="center">
|
||||
<img src="./assets/example_avatar.svg" height="100" width="auto" alt="PixelNebula Demo" />
|
||||
<br/>
|
||||
<a href="./assets/example_avatar.svg">Sample Avatar Display</a><br><code><a href="./pixelnebula_test.go">Sample Code</a></code>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
## ✨ Features
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/diversify.svg" height="60" width="60" alt="Various Styles" /><br/>
|
||||
<strong>Various Styles</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/animation.svg" height="60" width="60" alt="Animation Effects" /><br/>
|
||||
<strong>Animation Effects</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/customize.svg" height="60" width="60" alt="Customizable" /><br/>
|
||||
<strong>Customizable</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/performance.svg" height="60" width="60" alt="High Performance" /><br/>
|
||||
<strong>High Performance</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/cache.svg" height="60" width="60" alt="Cache System" /><br/>
|
||||
<strong>Cache System</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
- 🎨 **Various Styles and Themes**: Built-in multiple styles and theme combinations to meet different design needs
|
||||
- 🔄 **Rich Animation Effects**: Support for rotation, gradient, transformation, fade-in/out, and other animations
|
||||
- 🛠️ **Fully Customizable**: Custom styles, themes, colors, and animation effects
|
||||
- ⚡ **High-Performance Design**: Optimized code structure and caching mechanism for fast generation
|
||||
- 💾 **Smart Cache System**: Built-in cache system to improve efficiency of repetitive generation
|
||||
- 📊 **Cache Monitoring**: Support for cache usage monitoring and analysis
|
||||
- 🔍 **Chainable API**: Clean and clear API design with support for fluent chaining
|
||||
- 📱 **Responsive Design**: Support for custom sizes, adapting to various display environments
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
Using Go toolchain to install the package:
|
||||
|
||||
```bash
|
||||
go get github.com/landaiqing/go-pixelnebula
|
||||
```
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Below is a basic example of generating a simple SVG avatar:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a new PixelNebula instance
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// Set style and size
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
// Generate SVG and save to file
|
||||
svg, err := pn.Generate("unique-id-123", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to generate SVG: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Save to file
|
||||
err = os.WriteFile("my_avatar.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to save file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Avatar successfully generated: my_avatar.svg")
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
<td width="250">
|
||||
<div align="center">
|
||||
<img src="./assets/example_avatar_1.svg" alt="Avatar Example" width="auto" height="100"/>
|
||||
<p><a href="./assets/example_avatar_1.svg">Generated Avatar Example</a></p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🔧 Advanced Usage
|
||||
|
||||
### Custom Themes and Styles
|
||||
|
||||
<details open>
|
||||
<summary><strong>Click to Expand/Collapse Code Example</strong></summary>
|
||||
|
||||
```go
|
||||
// Custom style
|
||||
customStyles := []style.StyleSet{
|
||||
{
|
||||
// First custom style
|
||||
style.TypeEnv: `<circle cx="50%" cy="50%" r="48%" fill="#FILL0;"></circle>`,
|
||||
style.TypeHead: `<circle cx="50%" cy="50%" r="35%" fill="#FILL0;"></circle>`,
|
||||
style.TypeClo: `<rect x="25%" y="65%" width="50%" height="30%" fill="#FILL0;"></rect>`,
|
||||
style.TypeEyes: `<circle cx="40%" cy="45%" r="5%" fill="#FILL0;"></circle><circle cx="60%" cy="45%" r="5%" fill="#FILL1;"></circle>`,
|
||||
style.TypeMouth: `<path d="M 40% 60% Q 50% 70% 60% 60%" stroke="#FILL0;" stroke-width="2" fill="none"></path>`,
|
||||
style.TypeTop: `<path d="M 30% 30% L 50% 10% L 70% 30%" stroke="#FILL0;" stroke-width="4" fill="#FILL1;"></path>`,
|
||||
},
|
||||
}
|
||||
|
||||
// Apply custom style
|
||||
pn2.WithCustomizeStyle(customStyles)
|
||||
// Custom theme
|
||||
customThemes := []theme.Theme{
|
||||
{
|
||||
theme.ThemePart{
|
||||
// Environment part colors
|
||||
"env": []string{"#FF5733", "#C70039"},
|
||||
// Head colors
|
||||
"head": []string{"#FFC300", "#FF5733"},
|
||||
// Clothes colors
|
||||
"clo": []string{"#2E86C1", "#1A5276"},
|
||||
// Eyes colors
|
||||
"eyes": []string{"#000000", "#FFFFFF"},
|
||||
// Mouth colors
|
||||
"mouth": []string{"#E74C3C"},
|
||||
// Top decoration colors
|
||||
"top": []string{"#884EA0", "#7D3C98"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pn.WithCustomizeTheme(customTheme)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Using SVGBuilder Chainable API
|
||||
|
||||
<details open>
|
||||
<summary><strong>Click to Expand/Collapse Code Example</strong></summary>
|
||||
|
||||
```go
|
||||
pn := NewPixelNebula().WithDefaultCache()
|
||||
pn.Generate("my-avatar", false).
|
||||
SetStyle(style.GirlStyle).
|
||||
SetTheme(0).
|
||||
SetSize(231, 231).
|
||||
SetRotateAnimation("env", 0, 360, 10, -1).
|
||||
SetGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true).
|
||||
Build().
|
||||
ToSVG()
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 💫 Animation Effects
|
||||
|
||||
PixelNebula supports multiple animation effects to bring your avatars to life:
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_2.svg" height="120" width="120" alt="Rotation Animation" /><br/>
|
||||
<a href="./assets/example_avatar_2.svg">Rotation</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_3.svg" height="120" width="120" alt="Gradient Animation" /><br/>
|
||||
<a href="./assets/example_avatar_3.svg">Gradient</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_4.svg" height="120" width="120" alt="Fade In/Out" /><br/>
|
||||
<a href="./assets/example_avatar_4.svg">Fade In/Out</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_5.svg" height="120" width="120" alt="Path Animation" /><br/>
|
||||
<a href="./assets/example_avatar_5.svg">Path</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary><strong>Click to View Animation Code Examples</strong></summary>
|
||||
|
||||
```go
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// Set style
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 1. Rotation animation - Rotate environment and head
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, -1) // Infinite loop environment rotation
|
||||
|
||||
// 2. Gradient animation - Environment gradient
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
// 2. Gradient animation - Eyes gradient
|
||||
pn.WithGradientAnimation("eyes", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
|
||||
// 3. Fade in/out animation - Eyes blinking
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, -1)
|
||||
|
||||
// 4. Transform animation - Mouth scaling
|
||||
//pn.WithTransformAnimation("mouth", "scale", "1 1", "1.2 1.2", 1, -1)
|
||||
|
||||
// 5. Color animation - Hair color change
|
||||
pn.WithColorAnimation("top", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
// 5. Color animation - Clothes color change
|
||||
pn.WithColorAnimation("clo", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
|
||||
// 6. Bounce animation - Mouth bouncing
|
||||
pn.WithBounceAnimation("mouth", "transform", "0,0", "0,-10", 5, 2.5, -1)
|
||||
// 6. Rotation animation - Mouth rotation
|
||||
pn.WithRotateAnimation("mouth", 0, 360, 10, -1) // Infinite loop mouth rotation
|
||||
|
||||
//// 7. Wave animation - Clothes wave effect
|
||||
//pn.WithWaveAnimation("clo", 5, 0.2, "horizontal", 4, -1)
|
||||
|
||||
// 8. Blink animation - Top decoration blinking
|
||||
//pn.WithBlinkAnimation("head", 0.3, 1.0, 4, 6, -1)
|
||||
// 8. Wave animation - Environment wave effect
|
||||
//pn.WithWaveAnimation("clo", 5, 2, "horizontal", 4, -1)
|
||||
|
||||
// 9. Path animation - Eyes moving along path
|
||||
//pn.WithPathAnimation("eyes", "M 0,0 C 10,-10 -10,-10 0,0", 3, -1)
|
||||
|
||||
pn.WithBounceAnimation("eyes", "transform", "0,0", "0,-5", 5, 2, -1)
|
||||
|
||||
// 10. Path animation with rotation - Eyes rotating while moving
|
||||
//pn.WithPathAnimationRotate("eyes", "M 0,0 C 5,5 -5,5 0,0", "auto", 4, -1)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## ⚡ Cache System
|
||||
|
||||
PixelNebula has a built-in smart cache system to improve the efficiency of repeated generation:
|
||||
|
||||
<details>
|
||||
<summary><strong>Click to View Cache Configuration Code Examples</strong></summary>
|
||||
|
||||
```go
|
||||
// Use default cache configuration
|
||||
pn.WithDefaultCache()
|
||||
|
||||
// Custom cache configuration
|
||||
customCacheOptions := cache.CacheOptions{
|
||||
Enabled: true,
|
||||
DirectorySize: 100, // Maximum cache entries
|
||||
Expiration: 1 * time.Hour, // Cache expiration time
|
||||
//... Other configuration options
|
||||
}
|
||||
// Create a PixelNebula instance with custom cache
|
||||
pn := pixelnebula.NewPixelNebula().WithCache(customCacheOptions)
|
||||
|
||||
// Enable cache monitoring
|
||||
pn.WithMonitoring(cache.MonitorOptions{
|
||||
Enabled: true,
|
||||
SampleInterval: 5 * time.Second,
|
||||
//... Other configuration options
|
||||
})
|
||||
|
||||
// Enable cache compression
|
||||
pn.WithCompression(cache.CompressOptions{
|
||||
Enabled: true,
|
||||
Level: 6,
|
||||
MinSizeBytes: 100, // Minimum compression size (bytes)
|
||||
//... Other configuration options
|
||||
})
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📊 Benchmarks
|
||||
|
||||
We have conducted comprehensive benchmark tests on PixelNebula to ensure high performance in various scenarios. Below are sample test results (Test environment: Intel i7-12700K, 32GB RAM):
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Operation</th>
|
||||
<th>Time per Operation</th>
|
||||
<th>Memory Allocation</th>
|
||||
<th>Allocations</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Basic Avatar Generation</td>
|
||||
<td>3.5 ms/op</td>
|
||||
<td>328 KB/op</td>
|
||||
<td>52 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>No-Environment Avatar</td>
|
||||
<td>2.8 ms/op</td>
|
||||
<td>256 KB/op</td>
|
||||
<td>48 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>With Rotation Animation</td>
|
||||
<td>4.2 ms/op</td>
|
||||
<td>384 KB/op</td>
|
||||
<td>62 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Using Cache (Hit)</td>
|
||||
<td>0.3 ms/op</td>
|
||||
<td>48 KB/op</td>
|
||||
<td>12 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Concurrent Generation (10)</td>
|
||||
<td>5.7 ms/op</td>
|
||||
<td>392 KB/op</td>
|
||||
<td>58 allocs/op</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### Running Benchmarks
|
||||
|
||||
To run benchmarks in your own environment, execute:
|
||||
|
||||
```bash
|
||||
cd benchmark
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
For more detailed benchmark information, see [benchmark/README.md](benchmark/README.md).
|
||||
|
||||
<hr/>
|
||||
|
||||
## 💡 Examples and Demos
|
||||
|
||||
### 📚 Example Code
|
||||
|
||||
We've prepared a complete set of example code covering all core features to help you get started quickly.
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/base_use.svg" width="100" height="100" alt="Basic Usage" /><br/>
|
||||
<strong>Basic Usage</strong><br/>
|
||||
<a href="examples/01_basic_usage.go">View Code</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/style.svg" width="100" height="100" alt="Styles & Themes" /><br/>
|
||||
<strong>Styles & Themes</strong><br/>
|
||||
<a href="examples/02_styles_and_themes.go">View Code</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/theme.svg" width="100" height="100" alt="Custom Theme" /><br/>
|
||||
<strong>Custom Theme</strong><br/>
|
||||
<a href="examples/03_custom_theme_and_style.go">View Code</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/animation.svg" width="100" height="100" alt="Animations" /><br/>
|
||||
<strong>Animations</strong><br/>
|
||||
<a href="examples/04_all_animations.go">View Code</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/api.svg" width="100" height="100" alt="Chain API" /><br/>
|
||||
<strong>Chain API</strong><br/>
|
||||
<a href="examples/05_svg_builder_chain.go">View Code</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/cache.svg" width="100" height="100" alt="Cache System" /><br/>
|
||||
<strong>Cache System</strong><br/>
|
||||
<a href="examples/06_cache_system.go">View Code</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/convert.svg" width="100" height="100" alt="Format Conversion" /><br/>
|
||||
<strong>Format Conversion</strong><br/>
|
||||
<a href="examples/07_format_conversion.go">View Code</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/random.svg" width="100" height="100" alt="Random Avatar Generator" /><br/>
|
||||
<strong>Random Avatar Generator</strong><br/>
|
||||
<a href="examples/08_random_avatar_generator.go">View Code</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<h1>...</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary><strong>📋 Detailed Example Descriptions</strong></summary>
|
||||
|
||||
1. **Basic Usage** [01_basic_usage.go](examples/01_basic_usage.go)
|
||||
- Create basic PixelNebula avatars
|
||||
- Generate regular avatars and no-environment avatars
|
||||
- Demonstrate basic configuration and error handling
|
||||
|
||||
2. **Styles and Themes** [02_styles_and_themes.go](examples/02_styles_and_themes.go)
|
||||
- Use different styles to generate avatars
|
||||
- Apply multiple theme combinations
|
||||
- Demonstrate cycling through styles and themes
|
||||
|
||||
3. **Custom Themes and Styles** [03_custom_theme_and_style.go](examples/03_custom_theme_and_style.go)
|
||||
- Create custom themes
|
||||
- Define custom styles
|
||||
- Demonstrate combining themes and styles
|
||||
|
||||
4. **Animation Effects** [04_all_animations.go](examples/04_all_animations.go)
|
||||
- Rotation animation
|
||||
- Gradient animation
|
||||
- Fade-in/out effects
|
||||
- Transform animation
|
||||
- Color transformation
|
||||
- Bounce effects
|
||||
- Wave animation
|
||||
- Blink effects
|
||||
- Path animation
|
||||
|
||||
5. **SVG Builder Chainable API** [05_svg_builder_chain.go](examples/05_svg_builder_chain.go)
|
||||
- Basic chainable calls
|
||||
- Chainable calls with animation
|
||||
- Direct save to file
|
||||
- Base64 conversion
|
||||
|
||||
6. **Cache System** [06_cache_system.go](examples/06_cache_system.go)
|
||||
- Using default cache
|
||||
- Custom cache configuration
|
||||
- Cache monitoring functionality
|
||||
- Compressed cache examples
|
||||
|
||||
7. **Format Conversion** [07_format_conversion.go](examples/07_format_conversion.go)
|
||||
- Base64 encoding
|
||||
- We haven't found a perfect solution for other formats yet, please feel free to make a PR
|
||||
|
||||
8. **Random Avatar Generator** [08_random_avatar_generator.go](examples/08_random_avatar_generator.go)
|
||||
- Generate random avatars with different styles and themes
|
||||
|
||||
</details>
|
||||
|
||||
### 🎮 Running Examples
|
||||
|
||||
<details>
|
||||
<summary><strong>📋 How to Run Examples</strong></summary>
|
||||
|
||||
1. **Prerequisites**
|
||||
- Ensure Go is installed (Go 1.16+ recommended)
|
||||
- Make sure GOPATH is properly set
|
||||
|
||||
2. **Clone the Repository**
|
||||
```bash
|
||||
git clone github.com/landaiqing/go-pixelnebula.git
|
||||
cd go-pixelnebula
|
||||
```
|
||||
|
||||
3. **Run a Single Example**
|
||||
```bash
|
||||
go run examples/01_basic_usage.go
|
||||
```
|
||||
|
||||
4. **Run All Examples**
|
||||
```bash
|
||||
for file in examples/*_*.go; do
|
||||
echo "🚀 Running: $file"
|
||||
go run $file
|
||||
echo "------------"
|
||||
done
|
||||
```
|
||||
|
||||
5. **Run Benchmarks**
|
||||
```bash
|
||||
cd benchmark
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
**💡 Tip:** Check the top comments in each example for detailed functionality and customization options.
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 👥 Contribution Guide
|
||||
|
||||
Contributions of code, issue reports, or suggestions are welcome! Please follow these steps:
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="./assets/step_1.svg" width="60" height="60" alt="Step 1" /><br/>
|
||||
<strong>Fork the Repository</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_2.svg" width="60" height="60" alt="Step 2" /><br/>
|
||||
<strong>Create a Branch</strong><br/>
|
||||
<code>git checkout -b feature/amazing-feature</code>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_3.svg" width="60" height="60" alt="Step 3" /><br/>
|
||||
<strong>Commit Changes</strong><br/>
|
||||
<code>git commit -m 'Add some feature'</code>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_4.svg" width="60" height="60" alt="Step 4" /><br/>
|
||||
<strong>Push Branch</strong><br/>
|
||||
<code>git push origin feature/amazing-feature</code>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_5.svg" width="60" height="60" alt="Step 5" /><br/>
|
||||
<strong>Open PR</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
<hr/>
|
||||
|
||||
<div align="center">
|
||||
<p><strong>Made with ❤️ and Go</strong></p>
|
||||
<p>© 2025 landaiqing</p>
|
||||
|
||||
<br/>
|
||||
</div>
|
631
README_ZH.md
Normal file
@@ -0,0 +1,631 @@
|
||||
<div style="background-image: linear-gradient(rgba(26, 26, 26, 0.85), rgba(45, 45, 45, 0.85)), url('https://img.picui.cn/free/2025/03/19/67da3b51a5dea.png'); background-size: cover; background-position: center; background-repeat: no-repeat; padding: 40px 20px; border-radius: 15px; margin-bottom: 30px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);">
|
||||
|
||||
# <div align="center" style="display: flex; justify-content: center; align-items: center; margin-bottom: 20px;"><img src="./assets/pixel_planet.gif" height="40"/><span style="font-family: 'Press Start 2P', monospace; background: linear-gradient(45deg, #FF6B6B, #4ECDC4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 2px 2px 4px rgba(0,0,0,0.2);"> PixelNebula </span></div>
|
||||
|
||||
<div align="center" style="margin-bottom: 20px;">
|
||||
<p style="color: #ffffff;"><strong>中文</strong> | <a href="README.md" style="color: #4ECDC4;">English</a></p>
|
||||
</div>
|
||||
|
||||
<p align="center" style="margin-bottom: 20px;">
|
||||
<img src="./assets/golang_logo.gif" alt="PixelNebula Logo" height="150" style="border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.3);" />
|
||||
</p>
|
||||
|
||||
<div align="center" style="margin-bottom: 20px;">
|
||||
<p style="color: #ffffff; font-size: 1.2em;">
|
||||
<strong>🚀 一个强大、高效且可自定义的 Go 语言 SVG 动态头像生成库</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div align="center" style="margin-bottom: 20px; padding: 15px; border-radius: 10px;">
|
||||
<a href="https://pkg.go.dev/github.com/landaiqing/go-pixelnebula"><img src="https://img.shields.io/badge/go-reference-blue?style=flat-square&logo=go" alt="Go Reference"></a>
|
||||
<a href="https://goreportcard.com/"><img src="https://img.shields.io/badge/go%20report-A+-brightgreen?style=flat-square&logo=go" alt="Go Report Card"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square&logo=github" alt="License"></a>
|
||||
<a href="https://github.com/landaiqing/go-pixelnebula/releases"><img src="https://img.shields.io/badge/version-1.0.0-blue?style=flat-square&logo=github" alt="Version"></a>
|
||||
<a href="http://makeapullrequest.com"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square&logo=github" alt="PRs Welcome"></a>
|
||||
</div>
|
||||
|
||||
<p align="center" style="color: #ffffff;background: rgba(255, 255, 255, 0.1);padding: 15px; border-radius: 10px; ">
|
||||
<a href="#-特性" style="color: #4ECDC4;">✨ 特性</a> •
|
||||
<a href="#-安装" style="color: #4ECDC4;">📦 安装</a> •
|
||||
<a href="#-快速开始" style="color: #4ECDC4;">🚀 快速开始</a> •
|
||||
<a href="#-高级用法" style="color: #4ECDC4;">🔧 高级用法</a> •
|
||||
<a href="#-动画效果" style="color: #4ECDC4;">💫 动画效果</a> •
|
||||
<a href="#-缓存系统" style="color: #4ECDC4;">⚡ 缓存系统</a> •
|
||||
<a href="#-基准测试" style="color: #4ECDC4;">📊 基准测试</a> •
|
||||
<a href="#-示例和演示" style="color: #4ECDC4;">💡 示例和演示</a> •
|
||||
<a href="#-贡献指南" style="color: #4ECDC4;">👥 贡献指南</a> •
|
||||
<a href="#-许可证" style="color: #4ECDC4;">📄 许可证</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📋 介绍
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<p><strong style="font-family: 'Press Start 2P', monospace; background: linear-gradient(45deg, #FF6B6B, #4ECDC4); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 2px 2px 4px rgba(0,0,0,0.2);">PixelNebula</strong> 是一个 Go 语言编写的高性能 SVG 头像生成库,专注于创建精美、可定制且高度可动态化的矢量头像。使用 PixelNebula,您可以轻松生成各种风格的头像,添加动画效果,并应用各种主题和样式变化。</p>
|
||||
<p>无论是为应用程序创建用户头像、生成唯一标识图标,还是制作动态可视化效果,PixelNebula 都能满足您的需求。</p>
|
||||
</td>
|
||||
<td width="380">
|
||||
<p align="center">
|
||||
<img src="./assets/example_avatar.svg" height="100" width="auto" alt="PixelNebula Demo" />
|
||||
<br/>
|
||||
<a href="./assets/example_avatar.svg">示例头像展示</a><br><code><a href="./pixelnebula_test.go">示例代码</a></code>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/diversify.svg" height="60" width="60" alt="多样化风格" /><br/>
|
||||
<strong>多样化风格</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/animation.svg" height="60" width="60" alt="动画效果" /><br/>
|
||||
<strong>动画效果</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/customize.svg" height="60" width="60" alt="可自定义" /><br/>
|
||||
<strong>可自定义</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/performance.svg" height="60" width="60" alt="高性能" /><br/>
|
||||
<strong>高性能</strong>
|
||||
</td>
|
||||
<td align="center" width="100">
|
||||
<img src="./assets/cache.svg" height="60" width="60" alt="缓存系统" /><br/>
|
||||
<strong>缓存系统</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
- 🎨 **多样化风格与主题**: 内置多种风格和主题组合,满足不同设计需求
|
||||
- 🔄 **丰富动画效果**: 支持旋转、渐变、变换、淡入淡出等多种动画
|
||||
- 🛠️ **完全可自定义**: 自定义风格、主题、颜色和动画效果
|
||||
- ⚡ **高性能设计**: 优化的代码结构和缓存机制,确保快速生成
|
||||
- 💾 **智能缓存系统**: 内置缓存系统,提高重复生成效率
|
||||
- 📊 **缓存监控**: 支持缓存使用情况监控和分析
|
||||
- 🔍 **链式调用 API**: 简洁明了的 API 设计,支持流畅的链式调用
|
||||
- 📱 **响应式设计**: 支持自定义尺寸,适应各种显示环境
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📦 安装
|
||||
|
||||
使用 Go 工具链安装包:
|
||||
|
||||
```bash
|
||||
go get github.com/landaiqing/go-pixelnebula
|
||||
```
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 基本用法
|
||||
|
||||
以下是生成简单 SVG 头像的基本示例:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建一个新的 PixelNebula 实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
// 生成 SVG 并保存到文件
|
||||
svg, err := pn.Generate("unique-id-123", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成 SVG 失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile("my_avatar.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("头像成功生成: my_avatar.svg")
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
<td width="250">
|
||||
<div align="center">
|
||||
<img src="./assets/example_avatar_1.svg" alt="Avatar Example" width="auto" height="100"/>
|
||||
<p><a href="./assets/example_avatar_1.svg">生成的头像示例</a></p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🔧 高级用法
|
||||
|
||||
### 自定义主题和风格
|
||||
|
||||
<details open>
|
||||
<summary><strong>点击展开/折叠代码示例</strong></summary>
|
||||
|
||||
```go
|
||||
// 自定义风格
|
||||
customStyles := []style.StyleSet{
|
||||
{
|
||||
// 第一种自定义风格
|
||||
style.TypeEnv: `<circle cx="50%" cy="50%" r="48%" fill="#FILL0;"></circle>`,
|
||||
style.TypeHead: `<circle cx="50%" cy="50%" r="35%" fill="#FILL0;"></circle>`,
|
||||
style.TypeClo: `<rect x="25%" y="65%" width="50%" height="30%" fill="#FILL0;"></rect>`,
|
||||
style.TypeEyes: `<circle cx="40%" cy="45%" r="5%" fill="#FILL0;"></circle><circle cx="60%" cy="45%" r="5%" fill="#FILL1;"></circle>`,
|
||||
style.TypeMouth: `<path d="M 40% 60% Q 50% 70% 60% 60%" stroke="#FILL0;" stroke-width="2" fill="none"></path>`,
|
||||
style.TypeTop: `<path d="M 30% 30% L 50% 10% L 70% 30%" stroke="#FILL0;" stroke-width="4" fill="#FILL1;"></path>`,
|
||||
},
|
||||
}
|
||||
|
||||
// 应用自定义风格
|
||||
pn2.WithCustomizeStyle(customStyles)
|
||||
// 自定义主题
|
||||
customThemes := []theme.Theme{
|
||||
{
|
||||
theme.ThemePart{
|
||||
// 环境部分颜色
|
||||
"env": []string{"#FF5733", "#C70039"},
|
||||
// 头部颜色
|
||||
"head": []string{"#FFC300", "#FF5733"},
|
||||
// 衣服颜色
|
||||
"clo": []string{"#2E86C1", "#1A5276"},
|
||||
// 眼睛颜色
|
||||
"eyes": []string{"#000000", "#FFFFFF"},
|
||||
// 嘴巴颜色
|
||||
"mouth": []string{"#E74C3C"},
|
||||
// 头顶装饰颜色
|
||||
"top": []string{"#884EA0", "#7D3C98"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pn.WithCustomizeTheme(customTheme)
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 使用 SVGBuilder 链式调用
|
||||
|
||||
<details open>
|
||||
<summary><strong>点击展开/折叠代码示例</strong></summary>
|
||||
|
||||
```go
|
||||
pn := NewPixelNebula().WithDefaultCache()
|
||||
pn.Generate("my-avatar", false).
|
||||
SetStyle(style.GirlStyle).
|
||||
SetTheme(0).
|
||||
SetSize(231, 231).
|
||||
SetRotateAnimation("env", 0, 360, 10, -1).
|
||||
SetGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true).
|
||||
Build().
|
||||
ToSVG()
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 💫 动画效果
|
||||
|
||||
PixelNebula 支持多种动画效果,可以让您的头像栩栩如生:
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_2.svg" height="120" width="120" alt="旋转动画" /><br/>
|
||||
<a href="./assets/example_avatar_2.svg">旋转动画</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_3.svg" height="120" width="120" alt="渐变动画" /><br/>
|
||||
<a href="./assets/example_avatar_3.svg">渐变动画</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_4.svg" height="120" width="120" alt="淡入淡出" /><br/>
|
||||
<a href="./assets/example_avatar_4.svg">淡入淡出</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<img src="./assets/example_avatar_5.svg" height="120" width="120" alt="路径动画" /><br/>
|
||||
<a href="./assets/example_avatar_5.svg">路径动画</a>
|
||||
</td>
|
||||
<td align="center" width="150">
|
||||
<h1>...</h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary><strong>点击查看动画代码示例</strong></summary>
|
||||
|
||||
```go
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 1. 旋转动画 - 让环境和头部旋转
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, -1) // 无限循环旋转环境
|
||||
|
||||
// 2. 渐变动画 - 让环境渐变
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
// 2. 渐变动画 - 让眼睛渐变
|
||||
pn.WithGradientAnimation("eyes", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
|
||||
// 3. 淡入淡出动画 - 让眼睛闪烁
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, -1)
|
||||
|
||||
// 4. 变换动画 - 让嘴巴缩放
|
||||
//pn.WithTransformAnimation("mouth", "scale", "1 1", "1.2 1.2", 1, -1)
|
||||
|
||||
// 5. 颜色变换动画 - 让头发颜色变换
|
||||
pn.WithColorAnimation("top", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
// 5. 颜色变换动画 - 让衣服颜色变换
|
||||
pn.WithColorAnimation("clo", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
|
||||
// 6. 弹跳动画 - 让嘴巴弹跳
|
||||
pn.WithBounceAnimation("mouth", "transform", "0,0", "0,-10", 5, 2.5, -1)
|
||||
// 6. 旋转动画 - 让嘴巴旋转
|
||||
pn.WithRotateAnimation("mouth", 0, 360, 10, -1) // 无限循环旋转环境
|
||||
|
||||
//// 7. 波浪动画 - 让衣服产生波浪效果
|
||||
//pn.WithWaveAnimation("clo", 5, 0.2, "horizontal", 4, -1)
|
||||
|
||||
// 8. 闪烁动画 - 让头顶装饰闪烁
|
||||
//pn.WithBlinkAnimation("head", 0.3, 1.0, 4, 6, -1)
|
||||
// 8. 波浪动画 - 让环境产生波浪效果
|
||||
//pn.WithWaveAnimation("clo", 5, 2, "horizontal", 4, -1)
|
||||
|
||||
// 9. 路径动画 - 让眼睛沿着路径移动
|
||||
//pn.WithPathAnimation("eyes", "M 0,0 C 10,-10 -10,-10 0,0", 3, -1)
|
||||
|
||||
pn.WithBounceAnimation("eyes", "transform", "0,0", "0,-5", 5, 2, -1)
|
||||
|
||||
// 10. 带旋转的路径动画 - 让眼睛在移动的同时旋转
|
||||
//pn.WithPathAnimationRotate("eyes", "M 0,0 C 5,5 -5,5 0,0", "auto", 4, -1)
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## ⚡ 缓存系统
|
||||
|
||||
PixelNebula 内置智能缓存系统,提高重复生成的效率。
|
||||
|
||||
<details>
|
||||
<summary><strong>点击查看缓存配置代码示例</strong></summary>
|
||||
|
||||
```go
|
||||
// 使用默认缓存配置
|
||||
pn.WithDefaultCache()
|
||||
|
||||
// 自定义缓存配置
|
||||
customCacheOptions := cache.CacheOptions{
|
||||
Enabled: true,
|
||||
DirectorySize: 100, // 最大缓存条目数
|
||||
Expiration: 1 * time.Hour, // 缓存有效期
|
||||
//... 其他配置项
|
||||
}
|
||||
// 创建一个带自定义缓存的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula().WithCache(customCacheOptions)
|
||||
|
||||
// 启用缓存监控
|
||||
pn.WithMonitoring(cache.MonitorOptions{
|
||||
Enabled: true,
|
||||
SampleInterval: 5 * time.Second,
|
||||
//... 其他配置项
|
||||
})
|
||||
|
||||
// 启用缓存压缩
|
||||
pn.WithCompression(cache.CompressOptions{
|
||||
Enabled: true,
|
||||
Level: 6,
|
||||
MinSizeBytes: 100, // 最小压缩大小 (字节)
|
||||
//... 其他配置项
|
||||
})
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📊 基准测试
|
||||
|
||||
我们对 PixelNebula 进行了全面的基准测试,确保在各种场景下都能保持高性能表现。下面是测试结果示例(测试环境:Intel i7-12700K,
|
||||
32GB RAM):
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th>操作</th>
|
||||
<th>每次操作耗时</th>
|
||||
<th>内存分配</th>
|
||||
<th>分配次数</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>基本头像生成</td>
|
||||
<td>3.5 ms/op</td>
|
||||
<td>328 KB/op</td>
|
||||
<td>52 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>无环境头像</td>
|
||||
<td>2.8 ms/op</td>
|
||||
<td>256 KB/op</td>
|
||||
<td>48 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>添加旋转动画</td>
|
||||
<td>4.2 ms/op</td>
|
||||
<td>384 KB/op</td>
|
||||
<td>62 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>使用缓存(命中)</td>
|
||||
<td>0.3 ms/op</td>
|
||||
<td>48 KB/op</td>
|
||||
<td>12 allocs/op</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>并发生成(10)</td>
|
||||
<td>5.7 ms/op</td>
|
||||
<td>392 KB/op</td>
|
||||
<td>58 allocs/op</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### 运行基准测试
|
||||
|
||||
想要在自己的环境中运行基准测试,请执行:
|
||||
|
||||
```bash
|
||||
cd benchmark
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
更多详细的基准测试信息,请查看 [benchmark/README.md](benchmark/README.md)。
|
||||
|
||||
<hr/>
|
||||
|
||||
## 💡 示例和演示
|
||||
|
||||
### 📚 示例代码
|
||||
|
||||
我们准备了一套完整的示例代码,涵盖了 PixelNebula 的所有核心功能,帮助您快速上手使用。
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/base_use.svg" width="100" height="100" alt="基础用法" /><br/>
|
||||
<strong>基础用法</strong><br/>
|
||||
<a href="examples/01_basic_usage.go">查看代码</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/style.svg" width="100" height="100" alt="样式和主题" /><br/>
|
||||
<strong>样式和主题</strong><br/>
|
||||
<a href="examples/02_styles_and_themes.go">查看代码</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/theme.svg" width="100" height="100" alt="自定义主题" /><br/>
|
||||
<strong>自定义主题</strong><br/>
|
||||
<a href="examples/03_custom_theme_and_style.go">查看代码</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/animation.svg" width="100" height="100" alt="动画效果" /><br/>
|
||||
<strong>动画效果</strong><br/>
|
||||
<a href="examples/04_all_animations.go">查看代码</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/api.svg" width="100" height="100" alt="链式API" /><br/>
|
||||
<strong>链式API</strong><br/>
|
||||
<a href="examples/05_svg_builder_chain.go">查看代码</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/cache.svg" width="100" height="100" alt="缓存系统" /><br/>
|
||||
<strong>缓存系统</strong><br/>
|
||||
<a href="examples/06_cache_system.go">查看代码</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/convert.svg" width="100" height="100" alt="格式转换" /><br/>
|
||||
<strong>格式转换</strong><br/>
|
||||
<a href="examples/07_format_conversion.go">查看代码</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<img src="./assets/random.svg" width="100" height="100" alt="随机头像生成器" /><br/>
|
||||
<strong>随机头像生成器</strong><br/>
|
||||
<a href="examples/08_random_avatar_generator.go">查看代码</a>
|
||||
</td>
|
||||
<td align="center" width="200">
|
||||
<h1>...</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary><strong>📋 示例详细说明</strong></summary>
|
||||
|
||||
1. **基础用法** [01_basic_usage.go](examples/01_basic_usage.go)
|
||||
- 创建基本的 PixelNebula 头像
|
||||
- 生成常规头像和无环境头像
|
||||
- 演示基本配置和错误处理
|
||||
|
||||
2. **样式和主题** [02_styles_and_themes.go](examples/02_styles_and_themes.go)
|
||||
- 使用不同样式生成头像
|
||||
- 应用多种主题组合
|
||||
- 演示循环使用样式和主题
|
||||
|
||||
3. **自定义主题和样式** [03_custom_theme_and_style.go](examples/03_custom_theme_and_style.go)
|
||||
- 创建自定义主题
|
||||
- 定义自定义样式
|
||||
- 演示组合主题和样式
|
||||
|
||||
4. **动画效果** [04_all_animations.go](examples/04_all_animations.go)
|
||||
- 旋转动画
|
||||
- 渐变动画
|
||||
- 淡入淡出效果
|
||||
- 变换动画
|
||||
- 颜色变换
|
||||
- 弹跳效果
|
||||
- 波浪动画
|
||||
- 闪烁效果
|
||||
- 路径动画
|
||||
|
||||
5. **SVG构建器链式API** [05_svg_builder_chain.go](examples/05_svg_builder_chain.go)
|
||||
- 基本链式调用
|
||||
- 带动画的链式调用
|
||||
- 直接保存到文件
|
||||
- Base64转换
|
||||
|
||||
6. **缓存系统** [06_cache_system.go](examples/06_cache_system.go)
|
||||
- 使用默认缓存
|
||||
- 自定义缓存配置
|
||||
- 缓存监控功能
|
||||
- 压缩缓存示例
|
||||
|
||||
7. **格式转换** [07_format_conversion.go](examples/07_format_conversion.go)
|
||||
- Base64编码
|
||||
- 其他格式暂未找到完美解决方案,欢迎 PR
|
||||
|
||||
8. **随机头像生成器** [08_random_avatar_generator.go](examples/08_random_avatar_generator.go)
|
||||
- 随机生成不同风格和主题的头像
|
||||
|
||||
</details>
|
||||
|
||||
### 🎮 运行示例
|
||||
|
||||
<details>
|
||||
<summary><strong>📋 如何运行示例</strong></summary>
|
||||
|
||||
1. **运行准备**
|
||||
- 确保已安装 Go 环境(建议 Go 1.16+)
|
||||
- 正确设置 GOPATH
|
||||
|
||||
2. **克隆代码库**
|
||||
```bash
|
||||
git clone github.com/landaiqing/go-pixelnebula.git
|
||||
cd go-pixelnebula
|
||||
```
|
||||
|
||||
3. **运行单个示例**
|
||||
```bash
|
||||
go run examples/01_basic_usage.go
|
||||
```
|
||||
|
||||
4. **运行所有示例**
|
||||
```bash
|
||||
for file in examples/*_*.go; do
|
||||
echo "🚀 运行: $file"
|
||||
go run $file
|
||||
echo "------------"
|
||||
done
|
||||
```
|
||||
|
||||
5. **运行基准测试**
|
||||
```bash
|
||||
cd benchmark
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
**💡 提示:** 查看每个示例的顶部注释,了解功能详情和可自定义的部分。
|
||||
|
||||
</details>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 👥 贡献指南
|
||||
|
||||
欢迎贡献代码、报告问题或提出建议!请遵循以下步骤:
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="./assets/step_1.svg" height="60" alt="Step 1" /><br/>
|
||||
<strong>Fork 本仓库</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_2.svg" height="60" alt="Step 2" /><br/>
|
||||
<strong>创建分支</strong><br/>
|
||||
<code>git checkout -b feature/amazing-feature</code>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_3.svg" height="60" alt="Step 3" /><br/>
|
||||
<strong>提交更改</strong><br/>
|
||||
<code>git commit -m 'Add some feature'</code>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_4.svg" height="60" alt="Step 4" /><br/>
|
||||
<strong>推送分支</strong><br/>
|
||||
<code>git push origin feature/amazing-feature</code>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="./assets/step_5.svg" height="60" alt="Step 5" /><br/>
|
||||
<strong>开启 PR</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
该项目采用 MIT 许可证 - 详情请查看 [LICENSE](LICENSE) 文件。
|
||||
|
||||
<hr/>
|
||||
|
||||
<div align="center">
|
||||
<p><strong>用 ❤️ 和 Go 制作</strong></p>
|
||||
<p>© 2025 landaiqing</p>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
|
||||
|
75
animation/blink.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BlinkAnimation 闪烁动画
|
||||
type BlinkAnimation struct {
|
||||
BaseAnimation
|
||||
BlinkCount int // 闪烁次数
|
||||
MinOpacity float64 // 最小透明度
|
||||
MaxOpacity float64 // 最大透明度
|
||||
}
|
||||
|
||||
// NewBlinkAnimation 创建一个闪烁动画
|
||||
func NewBlinkAnimation(targetID string, minOpacity, maxOpacity float64, blinkCount int, duration float64, repeatCount int) *BlinkAnimation {
|
||||
return &BlinkAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Blink, // 闪烁动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
BlinkCount: blinkCount,
|
||||
MinOpacity: minOpacity,
|
||||
MaxOpacity: maxOpacity,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成闪烁动画的SVG代码
|
||||
func (a *BlinkAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建一个animate元素
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"opacity\" ", a.TargetID))
|
||||
|
||||
// 根据闪烁次数生成关键帧
|
||||
var keyTimes, values []string
|
||||
|
||||
// 计算关键帧
|
||||
for i := 0; i <= a.BlinkCount*2; i++ {
|
||||
// 计算关键帧时间点
|
||||
keyTime := float64(i) / float64(a.BlinkCount*2)
|
||||
keyTimes = append(keyTimes, fmt.Sprintf("%.2f", keyTime))
|
||||
|
||||
// 计算关键帧值,交替使用最大和最小透明度
|
||||
if i%2 == 0 {
|
||||
values = append(values, fmt.Sprintf("%.1f", a.MaxOpacity))
|
||||
} else {
|
||||
values = append(values, fmt.Sprintf("%.1f", a.MinOpacity))
|
||||
}
|
||||
}
|
||||
|
||||
// 添加关键帧属性
|
||||
sb.WriteString(fmt.Sprintf("keyTimes=\"%s\" ", strings.Join(keyTimes, ";")))
|
||||
sb.WriteString(fmt.Sprintf("values=\"%s\" ", strings.Join(values, ";")))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
105
animation/bounce.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BounceAnimation 弹跳动画
|
||||
type BounceAnimation struct {
|
||||
BaseAnimation
|
||||
Property string // 要变换的属性(如 y, transform 等)
|
||||
From string // 起始值
|
||||
To string // 结束值
|
||||
BounceCount int // 弹跳次数
|
||||
}
|
||||
|
||||
// NewBounceAnimation 创建一个弹跳动画
|
||||
func NewBounceAnimation(targetID string, property string, from, to string, bounceCount int, duration float64, repeatCount int) *BounceAnimation {
|
||||
return &BounceAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Bounce, // 弹跳动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Property: property,
|
||||
From: from,
|
||||
To: to,
|
||||
BounceCount: bounceCount,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成弹跳动画的SVG代码
|
||||
func (a *BounceAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建animateTransform元素,使用transform属性
|
||||
if a.Property == "transform" {
|
||||
sb.WriteString(fmt.Sprintf("<animateTransform href=\"#%s\" attributeName=\"transform\" type=\"translate\" ", a.TargetID))
|
||||
sb.WriteString(fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"%s\" ", a.TargetID, a.Property))
|
||||
sb.WriteString(fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To))
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
// 生成弹跳效果的关键帧
|
||||
var keyTimes, values []string
|
||||
step := 1.0 / float64(a.BounceCount*2)
|
||||
|
||||
for i := 0; i <= a.BounceCount*2; i++ {
|
||||
// 调整关键帧时间,使动画更加平滑
|
||||
keyTime := float64(i) * step
|
||||
// 为每个弹跳周期添加额外的中间帧
|
||||
if i > 0 && i < a.BounceCount*2 {
|
||||
keyTime = keyTime + (step * 0.1) // 稍微延长每次弹跳的时间
|
||||
}
|
||||
keyTimes = append(keyTimes, fmt.Sprintf("%.3f", keyTime))
|
||||
|
||||
if i%2 == 0 {
|
||||
values = append(values, a.From)
|
||||
} else {
|
||||
values = append(values, a.To)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加关键帧属性
|
||||
sb.WriteString(fmt.Sprintf("values=\"%s\" ", strings.Join(values, ";")))
|
||||
sb.WriteString(fmt.Sprintf("keyTimes=\"%s\" ", strings.Join(keyTimes, ";")))
|
||||
|
||||
// 添加缓动函数
|
||||
sb.WriteString("calcMode=\"spline\" ")
|
||||
sb.WriteString("keySplines=\"")
|
||||
for i := 0; i < len(values)-1; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteString(";")
|
||||
}
|
||||
if i%2 == 0 {
|
||||
// 快速上升
|
||||
sb.WriteString("0.2 0 0.8 1")
|
||||
} else {
|
||||
// 缓慢下落
|
||||
sb.WriteString("0.2 0.8 0.8 1")
|
||||
}
|
||||
}
|
||||
sb.WriteString("\" ")
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
// 添加fill属性
|
||||
sb.WriteString("fill=\"freeze\" />")
|
||||
|
||||
return sb.String()
|
||||
}
|
55
animation/color.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ColorAnimation 颜色变换动画
|
||||
type ColorAnimation struct {
|
||||
BaseAnimation
|
||||
FromColor string // 起始颜色
|
||||
ToColor string // 结束颜色
|
||||
Property string // 要变换的属性(fill 或 stroke)
|
||||
}
|
||||
|
||||
// NewColorAnimation 创建一个颜色变换动画
|
||||
func NewColorAnimation(targetID string, property string, fromColor, toColor string, duration float64, repeatCount int) *ColorAnimation {
|
||||
return &ColorAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Color, // 颜色变换动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
FromColor: fromColor,
|
||||
ToColor: toColor,
|
||||
Property: property,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成颜色变换动画的SVG代码
|
||||
func (a *ColorAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建一个animate元素
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"%s\" ", a.TargetID, a.Property))
|
||||
sb.WriteString(fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.FromColor, a.ToColor))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
50
animation/fade.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// FadeAnimation 淡入淡出动画
|
||||
type FadeAnimation struct {
|
||||
BaseAnimation
|
||||
From string // 起始透明度
|
||||
To string // 结束透明度
|
||||
}
|
||||
|
||||
// NewFadeAnimation 创建一个淡入淡出动画
|
||||
func NewFadeAnimation(targetID string, from, to string, duration float64, repeatCount int) *FadeAnimation {
|
||||
return &FadeAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Fade,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成淡入淡出动画的SVG代码
|
||||
func (a *FadeAnimation) GenerateSVG() string {
|
||||
// 创建一个animate元素,并将其添加到目标元素中
|
||||
svg := fmt.Sprintf("<animate href=\"#%s\" attributeName=\"opacity\" ", a.TargetID)
|
||||
svg += fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To)
|
||||
svg += fmt.Sprintf("dur=\"%gs\" ", a.Duration)
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
svg += "repeatCount=\"indefinite\" "
|
||||
} else if a.RepeatCount > 0 {
|
||||
svg += fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount)
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
svg += fmt.Sprintf("begin=\"%gs\" ", a.Delay)
|
||||
}
|
||||
|
||||
svg += "/>\n"
|
||||
|
||||
return svg
|
||||
}
|
73
animation/gradient.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GradientAnimation 渐变动画
|
||||
type GradientAnimation struct {
|
||||
BaseAnimation
|
||||
Colors []string // 渐变颜色列表
|
||||
Animate bool // 是否添加动画效果
|
||||
}
|
||||
|
||||
// NewGradientAnimation 创建一个渐变动画
|
||||
func NewGradientAnimation(targetID string, colors []string, duration float64, repeatCount int, animate bool) *GradientAnimation {
|
||||
return &GradientAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Gradient,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Colors: colors,
|
||||
Animate: animate,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成渐变动画的SVG代码
|
||||
func (a *GradientAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建渐变定义
|
||||
gradientID := fmt.Sprintf("%s-gradient", a.TargetID)
|
||||
sb.WriteString(fmt.Sprintf("<linearGradient id=\"%s\" x1=\"0%%\" y1=\"0%%\" x2=\"100%%\" y2=\"0%%\">\n", gradientID))
|
||||
|
||||
// 添加渐变颜色
|
||||
for i, color := range a.Colors {
|
||||
offset := float64(i) / float64(len(a.Colors)-1) * 100
|
||||
sb.WriteString(fmt.Sprintf(" <stop offset=\"%g%%\" stop-color=\"%s\" />\n", offset, color))
|
||||
}
|
||||
sb.WriteString("</linearGradient>\n")
|
||||
|
||||
// 为目标元素添加样式引用
|
||||
sb.WriteString(fmt.Sprintf("<style type=\"text/css\">\n #%s { fill: url(#%s) !important; }\n</style>\n", a.TargetID, gradientID))
|
||||
|
||||
// 添加动画
|
||||
if a.Animate {
|
||||
// x1 动画
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"x1\" from=\"0%%\" to=\"100%%\" ", gradientID))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
// x2 动画
|
||||
sb.WriteString(fmt.Sprintf("<animate href=\"#%s\" attributeName=\"x2\" from=\"100%%\" to=\"200%%\" ", gradientID))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
sb.WriteString("/>\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
62
animation/path.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PathAnimation 路径动画
|
||||
type PathAnimation struct {
|
||||
BaseAnimation
|
||||
Path string // SVG路径数据
|
||||
Rotate string // 是否旋转元素以跟随路径方向 ("auto", "auto-reverse", 或 "0")
|
||||
}
|
||||
|
||||
// NewPathAnimation 创建一个路径动画
|
||||
func NewPathAnimation(targetID string, path string, duration float64, repeatCount int) *PathAnimation {
|
||||
return &PathAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Path, // 路径动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Path: path,
|
||||
Rotate: "0", // 默认不旋转
|
||||
}
|
||||
}
|
||||
|
||||
// WithRotate 设置是否旋转元素以跟随路径方向
|
||||
func (a *PathAnimation) WithRotate(rotate string) *PathAnimation {
|
||||
a.Rotate = rotate
|
||||
return a
|
||||
}
|
||||
|
||||
// GenerateSVG 生成路径动画的SVG代码
|
||||
func (a *PathAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 创建一个animateMotion元素
|
||||
sb.WriteString(fmt.Sprintf("<animateMotion href=\"#%s\" ", a.TargetID))
|
||||
sb.WriteString(fmt.Sprintf("path=\"%s\" ", a.Path))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
// 设置旋转属性
|
||||
sb.WriteString(fmt.Sprintf("rotate=\"%s\" ", a.Rotate))
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
61
animation/rotate.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RotateAnimation 旋转动画
|
||||
type RotateAnimation struct {
|
||||
BaseAnimation
|
||||
FromAngle float64 // 起始角度
|
||||
ToAngle float64 // 结束角度
|
||||
CenterX float64 // 旋转中心X坐标
|
||||
CenterY float64 // 旋转中心Y坐标
|
||||
}
|
||||
|
||||
// NewRotateAnimation 创建一个旋转动画
|
||||
func NewRotateAnimation(targetID string, fromAngle, toAngle float64, duration float64, repeatCount int) *RotateAnimation {
|
||||
return &RotateAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Rotate,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
FromAngle: fromAngle,
|
||||
ToAngle: toAngle,
|
||||
CenterX: 0,
|
||||
CenterY: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成旋转动画的SVG代码
|
||||
func (a *RotateAnimation) GenerateSVG() string {
|
||||
// 创建一个带有transform-box和transform-origin样式的g元素
|
||||
// 这个g元素将包裹目标元素及其相关元素
|
||||
svg := fmt.Sprintf("<g style=\"transform-box: fill-box; transform-origin: center;\">\n")
|
||||
|
||||
// 这里只添加animateTransform元素
|
||||
svg += fmt.Sprintf(" <animateTransform attributeName=\"transform\" attributeType=\"XML\" type=\"rotate\" ")
|
||||
// 不再需要指定中心点坐标,直接使用角度值
|
||||
svg += fmt.Sprintf("from=\"%g\" to=\"%g\" ", a.FromAngle, a.ToAngle)
|
||||
svg += fmt.Sprintf("dur=\"%gs\" ", a.Duration)
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
svg += "repeatCount=\"indefinite\" "
|
||||
} else if a.RepeatCount > 0 {
|
||||
svg += fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount)
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
svg += fmt.Sprintf("begin=\"%gs\" ", a.Delay)
|
||||
}
|
||||
|
||||
svg += "additive=\"sum\" />\n"
|
||||
|
||||
svg += "</g>\n"
|
||||
|
||||
return svg
|
||||
}
|
52
animation/transform.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TransformAnimation 变换动画
|
||||
type TransformAnimation struct {
|
||||
BaseAnimation
|
||||
TransformType string // 变换类型(scale, translate等)
|
||||
From string // 起始变换值
|
||||
To string // 结束变换值
|
||||
}
|
||||
|
||||
// NewTransformAnimation 创建一个变换动画
|
||||
func NewTransformAnimation(targetID string, transformType string, from, to string, duration float64, repeatCount int) *TransformAnimation {
|
||||
return &TransformAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Transform,
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
TransformType: transformType,
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成变换动画的SVG代码
|
||||
func (a *TransformAnimation) GenerateSVG() string {
|
||||
// 创建一个animateTransform元素,并将其添加到目标元素中
|
||||
svg := fmt.Sprintf("<animateTransform href=\"#%s\" attributeName=\"transform\" attributeType=\"XML\" type=\"%s\" ", a.TargetID, a.TransformType)
|
||||
svg += fmt.Sprintf("from=\"%s\" to=\"%s\" ", a.From, a.To)
|
||||
svg += fmt.Sprintf("dur=\"%gs\" ", a.Duration)
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
svg += "repeatCount=\"indefinite\" "
|
||||
} else if a.RepeatCount > 0 {
|
||||
svg += fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount)
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
svg += fmt.Sprintf("begin=\"%gs\" ", a.Delay)
|
||||
}
|
||||
|
||||
svg += "additive=\"sum\" />\n"
|
||||
|
||||
return svg
|
||||
}
|
125
animation/types.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AnimationType 表示动画类型
|
||||
type AnimationType string
|
||||
|
||||
// 预定义动画类型常量
|
||||
const (
|
||||
Rotate AnimationType = "rotate" // 旋转动画
|
||||
Gradient AnimationType = "gradient" // 渐变动画
|
||||
Transform AnimationType = "transform" // 变换动画
|
||||
Fade AnimationType = "fade" // 淡入淡出动画
|
||||
Path AnimationType = "path" // 路径动画
|
||||
Color AnimationType = "color" // 颜色变换动画
|
||||
Bounce AnimationType = "bounce" // 弹跳动画
|
||||
Wave AnimationType = "wave" // 波浪动画
|
||||
Blink AnimationType = "blink" // 闪烁动画
|
||||
)
|
||||
|
||||
// Animation 表示一个SVG动画接口
|
||||
type Animation interface {
|
||||
// GenerateSVG 生成动画的SVG代码
|
||||
GenerateSVG() string
|
||||
// GetTargetID 获取目标元素ID
|
||||
GetTargetID() string
|
||||
}
|
||||
|
||||
// BaseAnimation 基础动画结构,包含所有动画共有的属性
|
||||
type BaseAnimation struct {
|
||||
Type AnimationType // 动画类型
|
||||
Duration float64 // 动画持续时间(秒)
|
||||
RepeatCount int // 重复次数,-1表示无限重复
|
||||
Delay float64 // 延迟时间(秒)
|
||||
TargetID string // 目标元素ID
|
||||
Attributes map[string]string // 动画属性
|
||||
}
|
||||
|
||||
// GetTargetID 获取目标元素ID
|
||||
func (a *BaseAnimation) GetTargetID() string {
|
||||
return a.TargetID
|
||||
}
|
||||
|
||||
// Manager 动画管理器,负责管理所有动画
|
||||
type Manager struct {
|
||||
animations []Animation
|
||||
}
|
||||
|
||||
// GetAnimations 获取所有动画
|
||||
func (m *Manager) GetAnimations() []Animation {
|
||||
return m.animations
|
||||
}
|
||||
|
||||
// NewAnimationManager 创建一个新的动画管理器
|
||||
func NewAnimationManager() *Manager {
|
||||
return &Manager{
|
||||
animations: make([]Animation, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// AddAnimation 添加一个动画
|
||||
func (m *Manager) AddAnimation(animation Animation) {
|
||||
m.animations = append(m.animations, animation)
|
||||
}
|
||||
|
||||
// GenerateSVGAnimations 生成SVG动画代码
|
||||
func (m *Manager) GenerateSVGAnimations() string {
|
||||
if len(m.animations) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
// 添加SVG命名空间声明
|
||||
sb.WriteString("<defs>\n")
|
||||
|
||||
// 用于存储需要放在defs中的定义
|
||||
var defsContent strings.Builder
|
||||
// 用于存储需要直接添加到SVG中的动画元素
|
||||
var animationsContent strings.Builder
|
||||
// 用于存储旋转动画的映射,键为目标元素ID
|
||||
rotateAnimations := make(map[string]string)
|
||||
|
||||
// 处理所有动画
|
||||
for _, anim := range m.animations {
|
||||
svgCode := anim.GenerateSVG()
|
||||
if svgCode == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 根据动画类型决定放置位置
|
||||
switch a := anim.(type) {
|
||||
case *GradientAnimation:
|
||||
// 渐变定义需要放在defs中
|
||||
defsContent.WriteString(svgCode)
|
||||
case *RotateAnimation:
|
||||
// 旋转动画需要包裹目标元素,先存储起来
|
||||
// 提取animateTransform标签
|
||||
if start := strings.Index(svgCode, "<animateTransform"); start != -1 {
|
||||
if end := strings.Index(svgCode[start:], "/>"); end != -1 {
|
||||
rotateAnimations[a.GetTargetID()] = svgCode[start : start+end+2]
|
||||
}
|
||||
}
|
||||
default:
|
||||
// 其他动画元素直接添加到SVG中
|
||||
animationsContent.WriteString(svgCode)
|
||||
}
|
||||
}
|
||||
|
||||
// 只有当存在需要放在defs中的内容时才添加defs标签
|
||||
if defsContent.Len() > 0 {
|
||||
sb.WriteString(defsContent.String())
|
||||
sb.WriteString("</defs>\n")
|
||||
} else {
|
||||
// 如果没有需要放在defs中的内容,则不添加defs标签
|
||||
sb.Reset()
|
||||
}
|
||||
|
||||
// 添加直接放置的动画元素
|
||||
sb.WriteString(animationsContent.String())
|
||||
|
||||
return sb.String()
|
||||
}
|
81
animation/wave.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package animation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WaveAnimation 波浪动画
|
||||
type WaveAnimation struct {
|
||||
BaseAnimation
|
||||
Amplitude float64 // 波浪振幅
|
||||
Frequency float64 // 波浪频率
|
||||
Direction string // 波浪方向 ("horizontal" 或 "vertical")
|
||||
}
|
||||
|
||||
// NewWaveAnimation 创建一个波浪动画
|
||||
func NewWaveAnimation(targetID string, amplitude, frequency float64, direction string, duration float64, repeatCount int) *WaveAnimation {
|
||||
return &WaveAnimation{
|
||||
BaseAnimation: BaseAnimation{
|
||||
Type: Wave, // 波浪动画类型
|
||||
Duration: duration,
|
||||
RepeatCount: repeatCount,
|
||||
Delay: 0,
|
||||
TargetID: targetID,
|
||||
Attributes: make(map[string]string),
|
||||
},
|
||||
Amplitude: amplitude,
|
||||
Frequency: frequency,
|
||||
Direction: direction,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateSVG 生成波浪动画的SVG代码
|
||||
func (a *WaveAnimation) GenerateSVG() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 生成波浪路径
|
||||
path := a.generateWavePath()
|
||||
|
||||
// 创建一个animateMotion元素
|
||||
sb.WriteString(fmt.Sprintf("<animateMotion href=\"#%s\" ", a.TargetID))
|
||||
sb.WriteString(fmt.Sprintf("path=\"%s\" ", path))
|
||||
sb.WriteString(fmt.Sprintf("dur=\"%gs\" ", a.Duration))
|
||||
|
||||
if a.RepeatCount < 0 {
|
||||
sb.WriteString("repeatCount=\"indefinite\" ")
|
||||
} else if a.RepeatCount > 0 {
|
||||
sb.WriteString(fmt.Sprintf("repeatCount=\"%d\" ", a.RepeatCount))
|
||||
}
|
||||
|
||||
if a.Delay > 0 {
|
||||
sb.WriteString(fmt.Sprintf("begin=\"%gs\" ", a.Delay))
|
||||
}
|
||||
|
||||
sb.WriteString("/>\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// generateWavePath 生成波浪路径
|
||||
func (a *WaveAnimation) generateWavePath() string {
|
||||
var path strings.Builder
|
||||
path.WriteString("M0,0 ")
|
||||
|
||||
// 生成正弦波路径
|
||||
points := 20 // 路径点数量
|
||||
for i := 0; i <= points; i++ {
|
||||
x := float64(i) / float64(points) * 100 // 0-100 范围
|
||||
// 计算正弦波 y 值
|
||||
y := a.Amplitude * math.Sin(a.Frequency*x*math.Pi/180)
|
||||
|
||||
if a.Direction == "horizontal" {
|
||||
path.WriteString(fmt.Sprintf("L%g,%g ", x, y))
|
||||
} else { // vertical
|
||||
path.WriteString(fmt.Sprintf("L%g,%g ", y, x))
|
||||
}
|
||||
}
|
||||
|
||||
return path.String()
|
||||
}
|
1
assets/animation.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742356591488" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="32018" width="200" height="200"><path d="M384.682667 174.08c10.581333 0 21.162667 7.168 32.426666 21.504l0.682667 1.024 0.341333 0.682667 0.341334 0.682666c23.210667 34.474667 63.146667 96.256 84.650666 128.341334 10.581333 17.066667 27.648 29.354667 47.104 34.474666l128 35.498667 1.706667 0.341333h0.341333c17.066667 6.826667 27.306667 15.36 30.037334 24.576 3.072 10.581333-0.341333 23.552-11.605334 39.253334l-93.866666 125.269333c-11.264 15.36-17.066667 34.133333-15.701334 53.248l7.168 140.970667v0.341333c-1.024 19.797333-5.12 33.109333-13.312 39.594667-4.437333 3.413333-9.898667 5.12-15.36 5.12-6.826667-0.341333-13.994667-1.706667-20.48-4.096l-143.018666-53.248c-8.533333-3.413333-17.749333-5.12-27.306667-5.12-10.922667 0-21.845333 2.048-32.085333 6.144L221.184 819.2c-10.24 4.096-20.821333 6.485333-31.744 7.168-4.778667 0.341333-9.216-1.365333-12.288-5.12-5.12-6.485333-7.509333-20.48-5.461333-40.96l4.096-160.768c1.024-19.456-5.12-38.229333-17.408-53.248-12.629333-14.677333-27.306667-34.474667-36.522667-46.08-15.701333-19.456-31.402667-38.229333-46.08-58.026667v-0.341333l-1.706667-3.072c-11.605333-18.773333-15.36-32.426667-11.264-43.690667 3.754667-8.533333 13.312-16.384 29.696-21.504L238.933333 348.501333c18.773333-6.144 34.816-18.773333 45.397334-35.498666l70.997333-117.077334c9.216-14.677333 19.114667-21.845333 29.354667-21.845333z" fill="#FFD156" p-id="32019"></path><path d="M384.682667 117.76c-21.845333 0-51.882667 9.557333-77.482667 48.469333L235.52 284.330667c-3.754667 5.12-8.874667 8.874667-15.018667 10.922666l-146.773333 44.714667c-34.474667 10.581333-57.344 30.378667-65.877333 57.344-9.216 25.6-4.096 56.32 16.042666 89.429333l2.048 4.096c0.682667 0.682667 1.365333 1.706667 2.048 2.389334 27.648 35.84 56.32 70.656 83.968 106.496 2.389333 6.826667 4.437333 12.970667 6.826667 20.138666l-4.096 156.672c-2.389333 35.157333 4.096 62.464 19.797333 80.554667 13.994667 15.701333 34.133333 24.576 55.296 24.234667 17.066667-0.341333 33.792-4.778667 49.152-12.629334l124.245334-49.834666c3.072-1.024 6.144-1.365333 9.216-1.365334 3.072 0 6.144 0.682667 8.874666 1.706667l144.384 53.248 2.048 0.682667h1.365334c11.946667 4.096 24.234667 6.144 36.522666 6.144 17.408 0 34.816-5.461333 49.152-15.701334 18.773333-13.994667 34.816-38.912 36.522667-82.944v-1.365333l-8.533333-143.36c0-5.802667 1.706667-11.264 5.12-16.042667l93.866666-127.317333c22.186667-30.378667 29.013333-60.416 20.48-88.405333-8.533333-28.672-31.061333-49.493333-67.242666-62.464l-1.365334-0.341334c-1.024-0.341333-2.389333-1.024-3.413333-1.024L563.2 305.493333c-5.802667-2.048-10.922667-6.144-14.677333-10.922666-27.989333-42.325333-55.978667-86.016-84.992-129.706667l-0.682667-1.365333-0.682667-1.024-0.682666-1.024c-21.162667-28.330667-48.810667-43.690667-76.8-43.690667zM709.973333 211.968h279.552c19.114667 0 28.672 9.557333 28.672 28.672V245.76c0 19.114667-9.557333 28.672-28.672 28.672H709.973333c-19.114667 0-28.672-9.557333-28.672-28.672v-5.12c0-19.114667 9.557333-28.672 28.672-28.672zM795.648 719.872h191.146667c19.114667 0 28.672 9.557333 28.672 28.672 0 19.114667-9.557333 28.672-28.672 28.672h-191.146667c-19.114667 0-28.672-9.557333-28.672-28.672 0-19.114667 9.557333-28.672 28.672-28.672zM891.221333 467.968h97.621334c19.114667 0 28.672 9.557333 28.672 28.672v5.12c0 19.114667-9.557333 28.672-28.672 28.672h-97.621334c-19.114667 0-28.672-9.557333-28.672-28.672v-5.12c0-19.114667 9.557333-28.672 28.672-28.672z" fill="#FFD156" p-id="32020"></path></svg>
|
After Width: | Height: | Size: 3.5 KiB |
1
assets/api.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742376867924" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="134948" width="200" height="200"><path d="M1002.666667 708.266667c0-34.133333-27.733333-64-64-64-34.133333 0-64 27.733333-64 64 0 27.733333 19.2 51.2 44.8 59.733333H917.333333v110.933333c0 23.466667-19.2 38.4-42.666666 38.4H149.333333c-23.466667 0-42.666667-14.933333-42.666666-38.4V512c-2.133333-10.666667-10.666667-19.2-21.333334-19.2s-21.333333 8.533333-21.333333 19.2V878.933333C64 925.866667 102.4 960 149.333333 960h725.333334c46.933333 0 85.333333-34.133333 85.333333-81.066667V768h-2.133333c25.6-8.533333 44.8-32 44.8-59.733333z m-64 42.666666c-23.466667 0-42.666667-19.2-42.666667-42.666666s19.2-42.666667 42.666667-42.666667 42.666667 19.2 42.666666 42.666667-19.2 42.666667-42.666666 42.666666zM147.2 302.933333c0-27.733333-17.066667-51.2-42.666667-59.733333V153.6C106.666667 130.133333 125.866667 106.666667 149.333333 106.666667h725.333334c23.466667 0 42.666667 23.466667 42.666666 46.933333V514.133333c0 12.8 8.533333 21.333333 21.333334 21.333334s21.333333-8.533333 21.333333-21.333334V153.6C960 106.666667 921.6 64 874.666667 64H149.333333C102.4 64 64 106.666667 64 153.6v89.6c-23.466667 8.533333-42.666667 32-42.666667 59.733333 0 34.133333 27.733333 64 64 64s61.866667-29.866667 61.866667-64z m-104.533333 0c0-23.466667 19.2-42.666667 42.666666-42.666666s42.666667 19.2 42.666667 42.666666-19.2 42.666667-42.666667 42.666667-42.666667-21.333333-42.666666-42.666667z" fill="#1296db" p-id="134949"></path><path d="M768 640h21.333333V362.666667h-21.333333zM539.733333 362.666667H512v277.333333h21.333333v-128h140.8c27.733333 0 51.2-34.133333 51.2-64v-29.866667c0-29.866667-23.466667-55.466667-51.2-55.466666h-134.4z m164.266667 55.466666V448c0 17.066667-14.933333 42.666667-29.866667 42.666667H533.333333v-106.666667h140.8c14.933333 0 29.866667 17.066667 29.866667 34.133333zM369.066667 362.666667h-23.466667c-42.666667 0-55.466667 38.4-59.733333 51.2L234.666667 640h23.466666l29.866667-128H426.666667l29.866666 128h23.466667l-49.066667-226.133333c-6.4-12.8-19.2-51.2-61.866666-51.2z m53.333333 128h-130.133333l14.933333-70.4c8.533333-25.6 21.333333-36.266667 42.666667-36.266667h12.8c21.333333 0 34.133333 10.666667 42.666666 38.4l17.066667 68.266667z" fill="#1296db" p-id="134950"></path></svg>
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/background.png
Normal file
After Width: | Height: | Size: 106 KiB |
1
assets/base_use.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742376582478" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="115852" width="200" height="200"><path d="M283.5968 307.2L512 405.0944 740.4032 307.2 512 209.3056 283.5968 307.2z m-150.1696-47.104l358.4-153.6a51.2 51.2 0 0 1 40.3456 0l358.4 153.6c41.3696 17.7664 41.3696 76.4416 0 94.208l-358.4 153.6a51.2 51.2 0 0 1-40.3456 0l-358.4-153.6c-41.3696-17.7664-41.3696-76.4416 0-94.208zM512 814.6944l338.2272-144.9472a51.2 51.2 0 1 1 40.3456 94.1056l-358.4 153.6a51.2 51.2 0 0 1-40.3456 0l-358.4-153.6a51.2 51.2 0 1 1 40.3456-94.1056L512 814.6944z" fill="#72D6A7" p-id="115853"></path><path d="M512 609.8944l338.2272-144.9472a51.2 51.2 0 1 1 40.3456 94.1056l-358.4 153.6a51.2 51.2 0 0 1-40.3456 0l-358.4-153.6a51.2 51.2 0 1 1 40.3456-94.1056L512 609.8944z" fill="#72D6A7" p-id="115854"></path></svg>
|
After Width: | Height: | Size: 846 B |
1
assets/cache.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742357047377" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="53980" width="200" height="200"><path d="M213.333333 85.333333a85.333333 85.333333 0 0 0-85.333333 85.333334v682.666666a85.333333 85.333333 0 0 0 85.333333 85.333334h20.693334v-90.069334a31.829333 31.829333 0 1 1 63.658666 0V938.666667h169.642667v-90.069334a31.829333 31.829333 0 1 1 63.616 0V938.666667h169.685333v-90.069334a31.829333 31.829333 0 1 1 63.616 0V938.666667H810.666667a85.333333 85.333333 0 0 0 85.333333-85.333334V170.666667a85.333333 85.333333 0 0 0-85.333333-85.333334H213.333333z" fill="#BBC7DF" p-id="53981"></path><path d="M298.666667 339.285333C298.666667 316.842667 317.781333 298.666667 341.333333 298.666667h341.333334c23.552 0 42.666667 18.176 42.666666 40.618666v162.56c0 22.442667-19.114667 40.618667-42.666666 40.618667H341.333333c-23.552 0-42.666667-18.176-42.666666-40.618667v-162.56z" fill="#05C059" p-id="53982"></path><path d="M234.666667 339.285333C234.666667 278.613333 285.44 234.666667 341.333333 234.666667h341.333334c55.893333 0 106.666667 43.946667 106.666666 104.618666v162.56c0 60.672-50.773333 104.618667-106.666666 104.618667H341.333333c-55.893333 0-106.666667-43.946667-106.666666-104.618667v-162.56zM341.333333 298.666667c-23.552 0-42.666667 18.176-42.666666 40.618666v162.56c0 22.442667 19.114667 40.618667 42.666666 40.618667h341.333334c23.552 0 42.666667-18.176 42.666666-40.618667v-162.56C725.333333 316.842667 706.218667 298.666667 682.666667 298.666667H341.333333z" fill="#FFFFFF" p-id="53983"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
assets/convert.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742377025302" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="145611" width="200" height="200"><path d="M578.784 399.456V189.984a64 64 0 0 1 64-64h204.032a64 64 0 0 1 64 64v209.472a64 64 0 0 1-64 64h-204.032a64 64 0 0 1-64-64zM243.904 290.848v60.96H176.864a6.4 6.4 0 0 0-4.864 10.56l95.168 110.336a6.4 6.4 0 0 0 9.664 0l95.168-110.304a6.4 6.4 0 0 0-4.864-10.56h-75.2V290.816q0-43.072 30.432-73.536 30.464-30.464 73.536-30.464h56.96a24 24 0 1 0 0-48h-56.96q-62.944 0-107.488 44.512-44.48 44.512-44.48 107.488z m408.096 394.56a6.4 6.4 0 0 0 4.864 10.56h75.2v60.992q0 43.104-30.432 73.536-30.464 30.464-73.536 30.464h-56.96a24 24 0 0 0 0 48h56.96q62.944 0 107.456-44.48 44.544-44.544 44.544-107.52V696h67.04a6.4 6.4 0 0 0 4.864-10.56l-95.168-110.336a6.4 6.4 0 0 0-9.664 0l-95.168 110.304z m-206.784-37.024a64 64 0 0 0-64-64H177.184a64 64 0 0 0-64 64v209.44a64 64 0 0 0 64 64h204.032a64 64 0 0 0 64-64v-209.44z" fill="#606266" p-id="145612"></path></svg>
|
After Width: | Height: | Size: 1003 B |
1
assets/customize.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742356763315" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="38259" width="200" height="200"><path d="M0 512c0 282.7648 229.2352 512 512 512s512-229.2352 512-512S794.7648 0 512 0 0 229.2352 0 512z" fill="#43B4D2" opacity=".1" p-id="38260"></path><path d="M472 592c15.104 0 29.0048 5.2992 40 14.1056V512H224v256c0 17.7024 14.2976 32 32 32h256v-94.1056a63.8464 63.8464 0 0 1-40 14.1056c-35.3024 0-64-28.6976-64-64s28.6976-64 64-64z" fill="#97D8E7" p-id="38261"></path><path d="M432 472c0 15.104-5.2992 29.0048-14.1056 40H512v-94.4a64.064 64.064 0 0 0 104.4992-49.6A64.064 64.064 0 0 0 512 318.4V224H256a31.9616 31.9616 0 0 0-32 32v256h94.1056a63.8464 63.8464 0 0 1-14.1056-40c0-35.3024 28.6976-64 64-64s64 28.6976 64 64zM592 552c0-15.104 5.2992-29.0048 14.1056-40H512v94.4a64.064 64.064 0 0 0-104.4992 49.6A64.064 64.064 0 0 0 512 705.6V800h256c17.7024 0 32-14.2976 32-32V512h-94.1056a63.8464 63.8464 0 0 1 14.1056 40c0 35.3024-28.6976 64-64 64s-64-28.6976-64-64z" fill="#43B4D2" p-id="38262"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
1
assets/diversify.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742356411973" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18912" width="200" height="200"><path d="M188.235294 933.647059C100.894118 933.647059 30.117647 862.870588 30.117647 775.529412S100.894118 617.411765 188.235294 617.411765 346.352941 688.188235 346.352941 775.529412 275.576471 933.647059 188.235294 933.647059z m0-271.058824C126.494118 662.588235 75.294118 713.788235 75.294118 775.529412S126.494118 888.470588 188.235294 888.470588s112.941176-51.2 112.941177-112.941176-51.2-112.941176-112.941177-112.941177zM835.764706 933.647059C748.423529 933.647059 677.647059 862.870588 677.647059 775.529412S748.423529 617.411765 835.764706 617.411765 993.882353 688.188235 993.882353 775.529412 923.105882 933.647059 835.764706 933.647059z m0-271.058824c-61.741176 0-112.941176 51.2-112.941177 112.941177s51.2 112.941176 112.941177 112.941176 112.941176-51.2 112.941176-112.941176-51.2-112.941176-112.941176-112.941177zM519.529412 376.470588C432.188235 376.470588 361.411765 305.694118 361.411765 218.352941S432.188235 60.235294 519.529412 60.235294 677.647059 131.011765 677.647059 218.352941 606.870588 376.470588 519.529412 376.470588z m0-271.058823c-61.741176 0-112.941176 51.2-112.941177 112.941176s51.2 112.941176 112.941177 112.941177 112.941176-51.2 112.941176-112.941177S581.270588 105.411765 519.529412 105.411765z" fill="#577BFF" p-id="18913"></path><path d="M504.470588 737.882353C417.129412 737.882353 346.352941 667.105882 346.352941 579.764706S417.129412 421.647059 504.470588 421.647059 662.588235 492.423529 662.588235 579.764706 591.811765 737.882353 504.470588 737.882353z m0-271.058824c-61.741176 0-112.941176 51.2-112.941176 112.941177s51.2 112.941176 112.941176 112.941176 112.941176-51.2 112.941177-112.941176-51.2-112.941176-112.941177-112.941177z" fill="#FA7F64" p-id="18914"></path><path d="M859.858824 665.6h-4.517648c-12.047059-1.505882-21.082353-13.552941-18.070588-25.6 3.011765-19.576471 4.517647-39.152941 4.517647-58.729412 0-106.917647-51.2-209.317647-137.035294-272.564706-10.541176-7.529412-12.047059-21.082353-4.517647-31.623529 7.529412-10.541176 21.082353-12.047059 31.62353-4.517647C829.741176 343.341176 888.470588 459.294118 888.470588 579.764706c0 22.588235-1.505882 45.176471-6.023529 66.258823-1.505882 10.541176-10.541176 19.576471-22.588235 19.576471zM143.058824 602.352941c-12.047059 0-22.588235-10.541176-22.588236-22.588235C120.470588 417.129412 224.376471 271.058824 377.976471 216.847059c12.047059-4.517647 24.094118 1.505882 28.611764 13.552941 4.517647 12.047059-1.505882 24.094118-13.552941 28.611765-135.529412 48.188235-227.388235 176.188235-227.388235 320.752941 0 12.047059-10.541176 22.588235-22.588235 22.588235zM504.470588 963.764706c-84.329412 0-164.141176-27.105882-231.905882-76.8-10.541176-7.529412-12.047059-21.082353-4.517647-31.62353 7.529412-10.541176 21.082353-12.047059 31.623529-4.517647 58.729412 45.176471 129.505882 67.764706 203.294118 67.764706 54.211765 0 105.411765-12.047059 153.6-36.141176 10.541176-6.023529 24.094118-1.505882 30.117647 10.541176 6.023529 10.541176 1.505882 24.094118-10.541177 30.117647-51.2 27.105882-109.929412 40.658824-171.670588 40.658824z" fill="#577BFF" p-id="18915"></path></svg>
|
After Width: | Height: | Size: 3.2 KiB |
34
assets/example_avatar.svg
Normal file
@@ -0,0 +1,34 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 231 231"><defs>
|
||||
<linearGradient id="env-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#3498db" />
|
||||
<stop offset="25%" stop-color="#2ecc71" />
|
||||
<stop offset="50%" stop-color="#f1c40f" />
|
||||
<stop offset="75%" stop-color="#e74c3c" />
|
||||
<stop offset="100%" stop-color="#9b59b6" />
|
||||
</linearGradient>
|
||||
<style type="text/css">
|
||||
#env { fill: url(#env-gradient) !important; }
|
||||
</style>
|
||||
<animate href="#env-gradient" attributeName="x1" from="0%" to="100%" dur="8s" repeatCount="indefinite" />
|
||||
<animate href="#env-gradient" attributeName="x2" from="100%" to="200%" dur="8s" repeatCount="indefinite" />
|
||||
<linearGradient id="eyes-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#3498db" />
|
||||
<stop offset="25%" stop-color="#2ecc71" />
|
||||
<stop offset="50%" stop-color="#f1c40f" />
|
||||
<stop offset="75%" stop-color="#e74c3c" />
|
||||
<stop offset="100%" stop-color="#9b59b6" />
|
||||
</linearGradient>
|
||||
<style type="text/css">
|
||||
#eyes { fill: url(#eyes-gradient) !important; }
|
||||
</style>
|
||||
<animate href="#eyes-gradient" attributeName="x1" from="0%" to="100%" dur="8s" repeatCount="indefinite" />
|
||||
<animate href="#eyes-gradient" attributeName="x2" from="100%" to="200%" dur="8s" repeatCount="indefinite" />
|
||||
</defs>
|
||||
<animate href="#eyes" attributeName="opacity" from="1" to="0.3" dur="2s" repeatCount="indefinite" />
|
||||
<animate href="#top" attributeName="fill" from="#9b59b6" to="#e74c3c" dur="3s" repeatCount="indefinite" />
|
||||
<animate href="#clo" attributeName="fill" from="#9b59b6" to="#e74c3c" dur="3s" repeatCount="indefinite" />
|
||||
<animateTransform href="#mouth" attributeName="transform" type="translate" from="0,0" to="0,-10" dur="2.5s" values="0,0;0,-10;0,0;0,-10;0,0;0,-10;0,0;0,-10;0,0;0,-10;0,0" keyTimes="0.000;0.110;0.210;0.310;0.410;0.510;0.610;0.710;0.810;0.910;1.000" calcMode="spline" keySplines="0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1" repeatCount="indefinite" fill="freeze" /><animateTransform href="#eyes" attributeName="transform" type="translate" from="0,0" to="0,-5" dur="2s" values="0,0;0,-5;0,0;0,-5;0,0;0,-5;0,0;0,-5;0,0;0,-5;0,0" keyTimes="0.000;0.110;0.210;0.310;0.410;0.510;0.610;0.710;0.810;0.910;1.000" calcMode="spline" keySplines="0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1;0.2 0 0.8 1;0.2 0.8 0.8 1" repeatCount="indefinite" fill="freeze" /><g style="transform-box: fill-box; transform-origin: center;">
|
||||
<path id='env' d="M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z" style="fill:#0df;"/><animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" dur="10s" repeatCount="indefinite" additive="sum" /></g>
|
||||
<path id='head' d="m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z" style="fill:#f2c280;"/><path id='clo' d="m141.89 195a114.79 114.79 0 0 1 38 16.5 115.55 115.55 0 0 1-128.47 0 114.79 114.79 0 0 1 38-16.5l15.75 15.75h21z" style="fill:#571e57;"/><path d="m146.4 196.14-17.4 17.44-1.17 1.17h-24.34l-1.18-1.17-17.43-17.44c1.49-0.41 3-0.79 4.51-1.14l4.67-1 12.74 12.74h17.69l12.73-12.74 4.67 1c1.52 0.35 3 0.73 4.51 1.14z" style="fill:#ff0;"/><path id='top' d="m108.37 22.019c-6.2698-12.829-17.151-13.396-18.949 1.1769-11.448-9.4583-26.021-4.483-20.361 12.422-12.251-7.9282-24.919 1.7761-17.076 20.853-27.08 2.3646-22.715 24.726-10.111 31.435-9.9002 3.3566-10.701 9.4006-8.464 14.497 2.6574 4.7842 9.0126 6.4737 11.545 9.6519-6.624 0.59419-8.4112 5.6011-5.7404 9.5192 1.6896 2.4787 5.2756 4.2218 8.5971 5.5455 1.0485 0.40658 3.702 1.2732 3.9053 2.4181 0.18744 1.2156-6.7884 3.0055-5.7281 5.2612 0.60648 1.4227 1.7764 2.7151 2.6466 3.7156 1.2807 1.6595 10.755 8.0351 9.4583 4.2049-1.0271-3.7234-2.2148-7.4682-3.1456-11.192-1.1662-5.3069-1.7868-10.721-1.102-16.156 1.4223-5.455 5.069-4.4265 7.7837-8.3588 3.5264-5.7505 2.0296-11.614 2.124-13.575 0.107-1.7868 1.5407-1.1876 3.1884-1.4337 4.3868-0.64196 7.0081-2.1185 8.8377-6.2698 0.77035-1.9259 0.62057-9.7578 0.52426-11.78 0.36378-4.6328 4.1835 0 6.548 0.64196 3.2633 0.88805 6.8797 0.21399 9.0731-2.5037 1.7547-2.3753 2.0864-2.8888 4.6114-0.80245 2.6856 2.2148 4.0979 3.1349 7.6929 3.274 5.5637 0.20329 8.7735-6.2698 11.32-5.6386 3.5201 0.87735 3.6057 5.4567 10.261 4.8682 2.386-0.20329 3.8304-0.86665 5.4032-2.6428 0.88805-0.99505 1.958-2.5037 3.4345-2.6214 1.4658-0.1177 2.3218 2.3646 3.0065 3.4452 1.1926 2.6755 4.0295 3.6513 6.2377 3.3168 1.958-0.17119 3.854-1.4115 5.4268-2.4707 0.99679-0.66102 1.8284-0.81128 1.9256 0.2071 0.29592 2.2271 0.0862 7.7025 0.1596 8.4821 0.10556 8.4609 5.37 10.569 13.223 10.333-0.31871 3.7464 0.0583 11.28 5.4353 14.562 3.9481 2.7604 6.6657 1.2732 6.7299 7.8534 7e-3 6.1914-0.43693 13.061-1.2946 18.189-0.69547 4.0444-1.2412 6.4838-2.5251 10.378-0.64196 1.9152-0.81315 1.9687 1.4123 1.0699 7.1472-3.1456 10.539-11.48 8.3562-18.842-0.43869-2.0436 0.84525-1.7226 2.8781-2.6106 9.5248-4.2363 8.1264-11.335-0.75967-14.273 11.988-3.0926 13.886-8.9002 6.6871-15.375 7.3077-5.9168 3.6378-16.177-2.8032-16.991 12.422-7.0937 5.7349-22.062-5.1036-18.499 4.1728-12.037-5.5637-26.203-21.121-16.894 6.9653-11.373 2.065-22.661-12.101-10.785-3.4559-18.382-15.14-16.584-23.902-5.018 0.09435-20.075-16.001-17.42-18.146-2.5892z" style="fill:#de3b00;"/><path d="m5.4353 80.502c7.4468 9.1373 15.632 8.8912 15.632 8.8912s-6.0772 3.7983-6.8369 9.8755c-0.75966 6.088 4.5579 9.6295 8.0994 10.646 3.5522 1.0058 7.0937-2.7925 7.0937-2.7925s-5.8312 10.646-1.5193 15.964c4.3012 5.3176 11.908 3.0386 11.908 3.0386s-5.3283 10.132 1.0057 14.187c5.8312 3.7234 18.542 7.6715 20.511 8.2706-6.0666-9.7472-9.576-21.249-9.576-33.575v-0.0428c0-35.201 28.546-63.747 63.747-63.747 35.212 0 63.758 28.546 63.758 63.747 0 12.476-3.5843 24.116-9.7899 33.949h0.53496s13.931-1.0057 16.21-9.3727c2.279-8.3562 0.75967-9.8756 0.75967-9.8756s10.635 2.0329 13.417-7.5966l2.7926-9.6295s10.132 0 10.892-7.083c0.75963-7.0937-7.0295-12.411-7.0295-12.411s11.459 0.82385 14.498-10.453c1.0164-3.7555 0.83456-8.2171 0.1391-12.497-17.665-41.161-58.569-69.995-106.18-69.995-30.632 0-60.034 12.187-81.679 33.831v0.0107c-13.171 13.171-22.833 29.22-28.386 46.66z" style="fill:#none;"/><path id='eyes' d="m145.38 95.628c-5.1601 2.2597-11.03 2.2597-16.19 0m-47.29 1.75c5.1755-2.2694 11.065-2.2694 16.24 0" style="fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#795548;"/><path d="m90.016 106.28c-4.4506-0.0105-6.6902 5.3657-3.5508 8.5195 3.1394 3.1539 8.5252 0.93887 8.5352-3.5117 0.0063-2.7522-2.2204-4.9898-4.9727-4.9961l-0.011719-0.01172zm47.281 0c-4.4506-0.0105-6.6902 5.3657-3.5508 8.5195 3.1394 3.1539 8.5252 0.93887 8.5352-3.5117 6e-3 -2.7522-2.2204-4.9898-4.9727-4.9961l-0.01171-0.01172z" style="fill:#000;"/><g style="transform-box: fill-box; transform-origin: center;">
|
||||
<path id='mouth' d="m115.68 160.64c7.08 0 13.11-4.93 15.46-11.84a2.14 2.14 0 0 0-1.51-2.6101 2.3 2.3 0 0 0-0.73995-0.0593h-26.42a2.12 2.12 0 0 0-2.31 1.9099 1.85 1.85 0 0 0 0.0593 0.73995c2.3401 6.9301 8.3802 11.86 15.46 11.86z" style="fill:#ff0000;"/><animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" dur="10s" repeatCount="indefinite" additive="sum" /></g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.3 KiB |
1
assets/example_avatar_1.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 231 231"><path id='env' d="M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z" style="fill:#00deae;"/><path id='head' d="m50 50h130v130h-130z" style="fill:#755227;"/><path id='clo' d="m141.75 194.98a114.79 114.78 0 0 1 38 16.498 115.53 115.52 0 0 1-128.46 0 114.79 114.78 0 0 1 38-16.498l15.71 15.748h21z" style="fill:#000;"/><path d="m70 200.88v20.77c-2.22-0.95325-4.3999-1.9698-6.5399-3.0496h-0.10088v-14.621c2.17-1.1 4.39-2.1399 6.64-3.0996z" style="fill:#435363;"/><path d="m161 200.88v20.77c1.9-0.80986 3.7702-1.6798 5.6201-2.5898l0.0989-0.0494 0.82005-0.40997h0.10088v-14.621c-2.17-1.1-4.39-2.1399-6.6402-3.0996z" style="fill:#435363;"/><polygon transform="matrix(1 0 0 .99987 4e-5 -3e-5)" points="97.32 201.93 115.5 223.72 133.68 201.93" style="fill:#000;"/><path d="m111.2 230.88 1.31-16.908c0.32992 1.2798 5.6399 1.2798 5.9999 0l1.3201 16.938c-1.4301 0.0494-2.8601 0.089-4.3 0.089s-2.87 0-4.3-0.089z" style="fill:#none;"/><path d="m115.49 201.79v0.0692l-7.55 12.678-7.0001 11.809-19.19-26.487c0.60999-0.42995 1.22-0.89985 1.8001-1.3899a52 51.993 0 0 0 10.07-10.619l21.79 13.878z" style="fill:#e7ecf2;"/><path d="m149.24 199.86-19.08 26.517-7.0001-11.809-7.57-12.678-0.0593-0.10086 21.94-13.998a52.21 52.203 0 0 0 10.08 10.699c0.58013 0.47009 1.1502 0.92002 1.7301 1.3399z" style="fill:#e7ecf2;"/><path id='top' d="m157.79 67.5a61.31 61.31 0 0 1-42.79 17.43h-55.7c18.16-37.74 68.27-46.85 98.49-17.43z" style="fill:#FFEB3B;"/><path d="m122.93 7.0078c-10.503-0.15729-21.09 1.6448-29.545 5.4316-17.141 7.8999-32.169 23.297-43.973 38.779-5.1703 6.8631-8.7779 13.46-8.1855 18.395 0.93114 12.312 10.372 26.483 11.068 36.9 15.663-72.081 105.99-70.452 124.91-7.0525l4e-3 0.0156c5.616-10.926 8.0682-20.188 8.352-27.653 0.43654-15.607-7.8088-21.149-21.735-28.249 1.7934-3.7704 1.7273-7.5023 2.0625-10.154-0.79964-7.8568-3.6796-13.51-10.43-17.758-5.9434-3.7404-13.06-6.0867-18.463-7.2266-4.5319-0.87895-9.2901-1.3562-14.064-1.4277z" style="fill:#FFEB3B;"/><path d="m42.426 75.338c0.52158 18.689 10.557 74.338-18.115 101.25 12.38 10.603 28.352 19.061 46.025 24.594 11.032-4.6874 22.88-7.4147 34.817-8.5046l0.0633-14.477c-22.49-4.3813-40.766-18.898-48.862-39.967-8.096-21.07-4.7931-44.72 9.2478-62.393zm124.67 2.7207c7.8997 10.886 11.743 24.64 11.787 37.441-0.36632 30.178-22.389 57.576-53.12 62.708l0.0238 14.471c12.282 1.1216 24.518 3.9888 35.825 8.9128 15.488-5.1448 30.007-13.325 42.396-25.043-13.136-22.051-23.282-63.045-18.694-101.55z" style="fill:#none;"/><path d="m143.61 46.383c-11.639 0.12482-20.998 1.8906-20.998 1.8906l-9 3.5059c0.63003-0.0191 1.2603-0.0289 1.8906-0.0293h0.0996c35.169 0.055 60.959 27.235 63.283 63.383 7.4e-4 31.157-22.742 57.213-53.106 63.079l-0.0216 14.498c11.567 1.0563 23.154 3.6067 33.887 8.0463 35.952-15.315 55.082-52.303 36.709-68.279-5.018-7.9035-10.44-15.409-9.5544-23.03 5.0545-50.452 0.39626-63.561-43.189-63.064zm-69.966 21.09c-15.286 3.244-17.096 3.73-31.734 6.6953 3.0304 13.081 3.0583 22.274 1.2085 30.012-3.8004 11.361-8.9712 19.787-12.286 28.764-6.8823 22.459-2.9157 31.982 12.093 46.165 8.6595 8.0693 19.861 16.209 30.939 20.647 2.669-1.0316 5.3729-1.9628 8.106-2.792 7.4979-2.275 15.388-3.6535 23.206-4.3673l0.0433-14.393c-23.933-4.5937-44.283-21.98-50.77-45.817-6.3319-23.265 0.51104-48.752 19.195-64.914z" style="fill:#FFEB3B;"/><path id='eyes' d="m97.56 107.84a10.63 10.63 0 0 1-15 0.13l-0.13-0.13" style="fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.3px;stroke:#000;"/><path d="m148.59 107.84a10.63 10.63 0 0 1-15 0.13l-0.13-0.13" style="fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.3px;stroke:#000;"/><path id='mouth' d="m115.5 161.71c-8.24 0-14.46-4.15-19.19-11.25 3.37-2.44 6.51-4.57 10-6.79a5.25 5.25 0 0 1 5.48-0.17 28.19 28.19 0 0 1 3.68 2.75 28.19 28.19 0 0 1 3.68-2.75 5.25 5.25 0 0 1 5.48 0.17c3.52 2.22 6.66 4.35 10 6.79-4.74 7.1-11 11.25-19.19 11.25z" style="fill:#ff9a84;"/></svg>
|
After Width: | Height: | Size: 3.9 KiB |
3
assets/example_avatar_2.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 231 231"><path id='env' d="M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z" style="fill:#4a3f73;"/><path id='head' d="m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z" style="fill:#b27e5b;"/><path id='clo' d="m116 203.13c-0.12 0-0.25 0.12-0.49 0.12s-0.25-0.12-0.49-0.12zm-27.29-8c0.87-0.25 1.72-0.47 2.56-0.69a32.37 32.37 0 0 0 0.3 8.57 21.5 21.5 0 0 0 7 6.88c6.41-6 16.8-6.64 16.8-6.64s10.5 0.58 17 6.69a21.61 21.61 0 0 0 6.93-6.66 32.34 32.34 0 0 0 0.35-8.84l2.13 0.56a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.64 114.64 0 0 1 37.38-16.37z" style="fill:#e6e9ee;"/><path d="m126.15 206-3.92 7.83h-13.46l-3.92-7.83a36.59 36.59 0 0 1 10.65-2.7 35.66 35.66 0 0 1 10.65 2.7z" style="fill:#f1543f;"/><path d="m124.54 230.65-2.18-16.74h-13.47l-2.19 16.76c2.9 0.22 5.84 0.33 8.8 0.33s6.06-0.12 9-0.35z" style="fill:#ff7058;"/><path d="m134.84 186s0.86 9.8-19.34 17.26c0 0 15.79 0.86 20.57 11.76 0.12 0.49 9.3-23.26-1.23-29z" style="fill:#fff;"/><path d="m96.16 186c-10.41 5.76-1.35 29.39-1.1 29 4.65-10.78 20.56-11.76 20.56-11.76-20.32-7.45-19.46-17.24-19.46-17.24z" style="fill:#fff;"/><path id='top' d="m156.1 15.879c-0.38556 5.3015-1.7049 9.4762-3.6602 12.76-0.41226 23.773-9.2343 35.229-15.154 42.797l15.062-4.6641c-0.66253 2.8135-2.4628 7.156-0.34766 12.137 1.6334-2.3144 7.9395-5.807 13-3.3477-0.43442 3.5532-0.95271 7.094-1.4512 10.639l8.9648 0.85937c0.83453 3.8792 0.51719 9.3449-0.59961 11.736l5.5508 2.0098c0.20764 2.7646 0.10001 5.4906-0.74609 8.875 8.4545-1.7225 14.213-4.3896 19.641-13.188 2.8639-4.7524 4.9018-10.483 4.7305-17.242-4.1612 4.916-9.6484 7.2485-15.26 10.109 6.507-11.065 8.8648-22.768 8.1367-30.58-7.3456 10.251-11.649 13.06-19.918 16.9 1.2386-11.4 5.5249-18.582 12.461-27.27-11.392-1.3025-16.301 1.4749-24.891 6.4395 4.5466-14.036 2.2208-26.679-5.5195-38.971zm-117.76 28.682c9.3378 3.6366 19.581 9.0234 21.129 18.549-7.6182 0.0414-14.897-3.5072-20.242-7.1894-0.15967 8.2309 2.8451 12.252 6.7734 19.08-7.2127 1.6129-12.084 4.8315-17.471 9.4805 7.2948-0.15715 12.299-1.0502 16.891 4.2793-6.0512 5.0164-11.99 10.79-11.99 19.24 9.257-6.1688 12.495-5.9486 21.137-2.2012 1.2906-8.0996 2.3978-14.872 2.7869-16.435 2.4719-0.73247 3.5247-0.94807 5.9221-1.2938-2.1556-7.4281 1.0996-9.5176 2.4141-11.6l7.543 1.5059c-3.9093-6.1699 2.6565-12.483 7.1445-15.51-4.4474-7.2082-5.6649-11.558-7.377-16.797-11.198-8.2947-23.895-6.2742-34.66-1.1094z" style="fill:#ffc;"/><path d="m101.9 7.6408c-10.047 6.2416-12.441 28.646-12.131 33.289-6.9249-5.8258-7.8992-13.75-7.7695-19.203-9.6235 6.0158-10.666 14.421-9 23.943 1.1061 5.1411 2.3972 10.461 7.377 16.797 2e-3 -1e-3 4e-3 -3e-3 6e-3 -4e-3 2.7742 2.8742 5.4644 5.5941 8.3477 8.3574 0.41187-6.971 0.45449-13.622 7.1856-15.824 3.9532 2.8169 7.4123 5.9388 11.084 9.1035l10.559-10.25c5.6447 3.961 5.4531 6.5652 6.5215 14.104 2.153-1.7546 8.719-9.0037 15.844-10.139 0.98706 4.1261-0.99388 10.308-2.6387 13.621 0 0 14.32-11.846 15.195-27.971 0.33968-6.2599 0.2237-11.146-0.041-14.826-3.2125 5.5652-8.7118 8.7799-13.789 10.15-4.2715-9.2486-2.4785-21.435-0.48047-29.309-12.21 3.0195-20.932 18.337-22.172 25.07-9.2678-7.397-13.605-16.146-14.098-26.91z" style="fill:#ffc;"/><g style="transform-box: fill-box; transform-origin: center;">
|
||||
<path id='eyes' d="m86.851 100.39a4.94 4.94 0 1 0 4.9297 5 5 5 0 0 0-4.9297-5zm57.221 0a4.94 4.94 0 1 0 4.9394 4.9394 4.94 4.94 0 0 0-4.9394-4.9394z" style="fill:#000;"/><path d="m86.207 89.365c-25.504 0-21.503 6.8561-21.035 19.596 0.80177 18.121 17.763 16.514 21.201 16.639 14.758-0.041 20.518-8.227 22.951-22.932 1.8166-10.731-9.251-13.174-23.117-13.303zm58.598 0c-13.866 0.1284-24.936 2.5717-23.119 13.303 2.4332 14.705 8.1936 22.891 22.951 22.932 3.4383-0.125 20.399 1.4828 21.201-16.639 0-18.965-0.47958-19.596-21.033-19.596z" style="fill:#000;"/><path d="m169.87 90.255a0.51 0.51 0 0 0-0.43991-0.52 167.64 167.64 0 0 0-22.6-1.6801c-12 0-27.47 3.7601-30.17 3.7601h-2.4c-1.2499 0-5.29-0.80996-10.45-1.6801a124.35 124.35 0 0 0-19.72-2.08 166.18 166.18 0 0 0-19.31 1.24c-1.56 0.17999-2.69 0.35009-3.2899 0.44009a0.51 0.51 0 0 0-0.44007 0.52l-0.091 6.4501a0.57 0.57 0 0 0 0.33012 0.52l0.73994 0.23992c1.08 0.41992 1.0001 19.85 6.78 24.71 3.4401 2.8599 6.51 4.4899 19.42 4.4899 7.4699 0 12.17-1.9999 16.63-8 3.21-4.32 6.0999-14.55 6.0999-14.55 0.82006-4.07 3.7702-4.52 4.43-4.5801h0.12068c0.11078 0 3.66 0.0593 4.57 4.5801 0 0 2.8599 10.22 6.0699 14.54 4.4601 5.9999 9.1601 8 16.63 8 12.91 0 16-1.63 19.42-4.4901 5.7898-4.86 5.6998-24.29 6.78-24.71l0.73994-0.23993a0.57 0.57 0 0 0 0.32996-0.52l-0.12068-6.4501zm-65 23c-1.9101 4.5-6.8 10.29-13.7 10.64-20.7 0.99985-21.65-4.7401-23-9.3201a31.45 31.45 0 0 1-1.2099-13.18c0.53997-4.5799 1.7-7.2699 3.7801-8.6201a9.3 9.3 0 0 1 4.3499-1.51 85.07 85.07 0 0 1 11.4-0.52 59.23 59.23 0 0 1 9.2099 0.69999c7.37 1.2 12.35 3.7001 12.35 6.1601a46.12 46.12 0 0 1-3.23 15.64zm58 1.3201c-1.34 4.5799-2.29 10.36-23 9.3201-6.91-0.3501-11.81-6.1401-13.71-10.64a46.35 46.35 0 0 1-3.22-15.64c0-3.39 9.43-6.8599 21.56-6.8599 12.13 0 14 0.89996 15.75 1.9999 2.08 1.3502 3.2398 4 3.77 8.6201a31.23 31.23 0 0 1-1.1601 13.17z" style="fill:#57FFFD;"/><animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0" to="360" dur="10s" repeatCount="indefinite" additive="sum" /></g>
|
||||
<path id='mouth' d="m118.57 165.14a8.66 8.66 0 0 0-2.76-4.23h-0.62a8 8 0 0 0-2.76 4.22c-0.52 1.89 2.07 10.61 2.76 12.53h0.62c0.64-1.76 3.19-10.82 2.76-12.52z" style="fill:#191919;"/><path d="m102.81 152.24a2.4921 2.4921 0 1 1 1.19-4.84l0.21 0.06a37.1 37.1 0 0 0 5.43 1.12 44.52 44.52 0 0 0 11.76 0 37.1 37.1 0 0 0 5.43-1.12 2.4903 2.4903 0 0 1 1.59 4.72l-0.21 0.06a43.08 43.08 0 0 1-6.15 1.29 48.55 48.55 0 0 1-13.08 0 42.79 42.79 0 0 1-6.17-1.29z" style="fill:#191919;"/></svg>
|
After Width: | Height: | Size: 5.8 KiB |
15
assets/example_avatar_3.svg
Normal file
After Width: | Height: | Size: 6.2 KiB |
2
assets/example_avatar_4.svg
Normal file
After Width: | Height: | Size: 5.7 KiB |
2
assets/example_avatar_5.svg
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
assets/golang_logo.gif
Normal file
After Width: | Height: | Size: 59 KiB |
1
assets/performance.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742356866699" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="41116" width="200" height="200"><path d="M361.344 670.976c49.6 50.432 55.36 104.576 0.384 160.448-34.048 34.56-89.152 55.36-165.056 64.256a51.648 51.648 0 0 1-56.704-54.016l0.32-3.584 1.088-8.96c9.28-72.32 29.44-125.248 62.08-158.528 55.04-55.808 108.288-49.92 157.888 0.384z m454.208-529.28l8.576 2.56 8.384 2.816a72 72 0 0 1 44.416 44.864c33.408 97.664 22.784 196.608-31.104 294.72a546.112 546.112 0 0 1-85.184 114.688l-9.6 9.792-9.024 8.896-0.256 2.752c-6.336 58.752-50.88 136.448-132.992 236.928l-7.104 8.64-13.12 15.68a30.272 30.272 0 0 1-51.2-8.064l-1.024-2.816-45.888-146.56-4.16-2.816c-25.792-18.368-50.368-38.4-73.6-60.032l-11.392-10.88-11.136-11.072a736.32 736.32 0 0 1-88.768-109.568l-2.56-3.968L149.312 480a31.232 31.232 0 0 1-14.464-49.152l1.792-2.048 1.92-1.792c122.944-107.392 213.696-160.64 278.656-156.224l4.224 0.32 2.432 0.32 5.44-5.568a549.76 549.76 0 0 1 92.672-74.752l10.752-6.656 9.92-5.76c90.112-51.136 181.504-63.68 272.896-36.928zM539.968 347.904a96 96 0 0 0 0.32 135.744 96 96 0 0 0 135.744 0.32 96 96 0 0 0-0.32-135.744 96 96 0 0 0-135.744-0.32z" fill="#ED7B2F" p-id="41117"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
assets/pixel_planet.gif
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
assets/pixel_planet_2.gif
Normal file
After Width: | Height: | Size: 108 KiB |
1
assets/random.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742377107248" class="icon" viewBox="0 0 1331 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="151466" width="200" height="200"><path d="M1211.111921 328.365543L789.685952 215.444795a127.5904 127.5904 0 0 0-156.265688 90.220037L520.499517 727.0908a127.5904 127.5904 0 0 0 90.220037 156.265688L1032.145522 996.277236A127.5904 127.5904 0 0 0 1188.41121 906.057199L1301.331958 484.631231A127.5904 127.5904 0 0 0 1211.111921 328.365543z" fill="#E76C48" p-id="151467"></path><path d="M909.575033 599.918461m-111.738062 64.512a129.024 129.024 0 1 1 223.476124-129.024 129.024 129.024 0 1 1-223.476124 129.024Z" fill="#FFE1E1" p-id="151468"></path><path d="M132.437333 123.767467L685.329067 6.2464a165.2736 165.2736 0 0 1 196.027733 127.317333l117.521067 552.8576a165.2736 165.2736 0 0 1-127.317334 196.027734L318.702933 999.970133a165.2736 165.2736 0 0 1-196.061866-127.317333L5.154133 319.829333a165.2736 165.2736 0 0 1 127.317334-196.061866z" fill="#FF8663" p-id="151469"></path><path d="M387.140267 681.233067a92.672 92.672 0 1 1-100.932267 155.4432 92.672 92.672 0 0 1 100.932267-155.4432z m163.498666-251.8016a92.672 92.672 0 1 1-100.932266 155.4432 92.672 92.672 0 0 1 100.9664-155.4432z m163.5328-251.8016a92.672 92.672 0 1 1-100.932266 155.4432 92.672 92.672 0 0 1 100.932266-155.4432z" fill="#FFFFFF" p-id="151470"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
assets/step_1.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742357971206" class="icon" viewBox="0 0 3794 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="92083" width="200" height="200"><path d="M529.468235 583.740235l22.889412-138.541176H89.750588l57.825883-404.781177h681.863529l-22.889412 196.367059H575.247059l12.047059-80.715294H366.832941l-34.93647 173.477647h462.607058l-45.778823 312.018824c0 11.264-2.831059 22.106353-8.432941 32.527058-4.818824 9.637647-11.625412 18.070588-20.48 25.298824a100.592941 100.592941 0 0 1-30.117647 16.865882c-10.420706 4.035765-21.684706 6.023529-33.731765 6.02353H54.814118l22.889411-185.524706 34.936471 46.983529h416.828235z m518.505412-439.717647l-22.889412-34.93647h254.192941l-15.661176 104.809411h143.36l-24.094118 115.651765h-133.722353l-39.755294 277.082353h115.651765l-22.889412 115.651765h-254.192941c-23.311059 0-42.164706-7.589647-56.621176-22.889412-14.456471-15.239529-18.853647-34.514824-13.251765-57.825882l46.983529-312.018824h-92.762353l12.047059-115.651765h93.967059l9.637647-69.872941z m1044.600471 369.844706H1722.729412l-12.047059 92.762353h324.065882l57.825883-45.778823-34.936471 161.430588h-508.385882c-23.311059 0-40.96-7.589647-53.007059-22.889412-11.264-15.239529-16.865882-34.514824-16.865882-57.825882l57.825882-427.670589h601.148235l-45.778823 299.971765z m-336.112942-184.32l-10.842352 68.668235h138.541176l12.047059-68.668235h-139.745883z m1187.84-115.651765l-57.825882 427.670589c-1.686588 12.047059-5.662118 22.889412-12.047059 32.527058-6.505412 9.637647-14.456471 18.070588-24.094117 25.298824a104.809412 104.809412 0 0 1-28.912942 16.865882c-9.637647 4.035765-18.913882 6.023529-27.708235 6.02353H2505.788235l-39.755294 252.988235H2234.729412l107.218823-726.437647-33.731764-34.936471h636.084705z m-381.891764 115.651765l-43.369412 287.924706h139.745882l42.164706-287.924706h-138.541176z m737.159529 392.734118h-228.894117l75.89647-539.708236-93.967059 13.251765 18.070589-125.289412 326.475294-45.778823-97.581177 697.524706z m263.529412-508.385883h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z m-42.164706 369.844706h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z" fill="#21BB9E" p-id="92084"></path></svg>
|
After Width: | Height: | Size: 2.1 KiB |
1
assets/step_2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742358069154" class="icon" viewBox="0 0 4216 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="96636" width="200" height="200"><path d="M532.058353 583.740235l22.889412-138.541176h-462.607059l57.825882-404.781177h681.86353l-22.889412 196.367059h-231.30353l12.047059-80.715294h-220.461176l-34.936471 173.477647h462.607059l-45.778823 312.018824c0 11.264-2.831059 22.106353-8.432942 32.527058-4.818824 9.637647-11.625412 18.070588-20.48 25.298824-8.854588 7.228235-18.853647 12.890353-30.117647 16.865882-10.420706 4.035765-21.684706 6.023529-33.731764 6.02353H57.404235l22.889412-185.524706 34.936471 46.983529h416.828235z m518.505412-439.717647l-22.889412-34.93647h254.192941l-15.661176 104.809411h143.36l-24.094118 115.651765h-133.722353l-39.755294 277.082353h115.651765l-22.889412 115.651765h-254.192941c-23.311059 0-42.164706-7.589647-56.621177-22.889412-14.456471-15.239529-18.853647-34.514824-13.251764-57.825882l46.983529-312.018824h-92.762353l12.047059-115.651765h93.967059l9.637647-69.872941z m1044.60047 369.844706h-369.844706l-12.047058 92.762353h324.065882l57.825882-45.778823-34.93647 161.430588h-508.385883c-23.311059 0-40.96-7.589647-53.007058-22.889412-11.264-15.239529-16.865882-34.514824-16.865883-57.825882l57.825883-427.670589h601.148235l-45.778824 299.971765z m-336.112941-184.32l-10.842353 68.668235h138.541177l12.047058-68.668235h-139.745882z m1187.84-115.651765l-57.825882 427.670589c-1.686588 12.047059-5.662118 22.889412-12.047059 32.527058-6.505412 9.637647-14.456471 18.070588-24.094118 25.298824-8.914824 7.228235-18.552471 12.890353-28.912941 16.865882-9.637647 4.035765-18.913882 6.023529-27.708235 6.02353h-287.924706l-39.755294 252.988235h-231.30353l107.218824-726.437647-33.731765-34.936471h636.084706z m-381.891765 115.651765l-43.369411 287.924706h139.745882l42.164706-287.924706h-138.541177z m1011.832471-179.501176h-230.098824l-10.842352 91.557647h-228.894118l28.912941-206.004706h685.477647l-57.825882 412.009412h-480.677647l-21.684706 160.225882h434.898823l51.802353-45.778824-22.889411 160.225883h-603.557648c-23.311059 0-41.743059-7.589647-55.41647-22.889412-13.673412-16.022588-18.853647-35.719529-15.661177-59.030588l43.369412-307.2h456.58353l26.503529-183.115294z m405.323294 63.849411h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z m-42.164706 369.844706h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z" p-id="96637" fill="#1296db"></path></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
assets/step_3.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742359677477" class="icon" viewBox="0 0 4096 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="100618" width="200" height="200"><path d="M487.725176 583.740235l22.889412-138.541176H48.007529l57.825883-404.781177h681.863529l-22.889412 196.367059h-231.303529l12.047059-80.715294h-220.461177l-34.93647 173.477647h462.607059l-45.778824 312.018824c0 11.264-2.831059 22.106353-8.432941 32.527058-4.818824 9.637647-11.685647 18.070588-20.48 25.298824a100.592941 100.592941 0 0 1-30.117647 16.865882c-10.480941 4.035765-21.684706 6.023529-33.731765 6.02353H13.071059l22.889412-185.524706 34.93647 46.983529h416.828235z m518.505412-439.717647l-22.889412-34.93647h254.192942l-15.661177 104.809411h143.36l-24.094117 115.651765h-133.722353l-39.755295 277.082353h115.651765l-22.889412 115.651765h-254.192941c-23.311059 0-42.164706-7.589647-56.621176-22.889412-14.456471-15.239529-18.913882-34.514824-13.251765-57.825882l46.983529-312.018824h-92.762352l12.047058-115.651765h93.967059l9.637647-69.872941z m1044.600471 369.844706h-369.844706l-12.047059 92.762353h324.065882l57.825883-45.778823-34.936471 161.430588h-508.385882c-23.311059 0-40.96-7.589647-53.007059-22.889412-11.264-15.239529-16.865882-34.514824-16.865882-57.825882l57.825882-427.670589h601.148235l-45.778823 299.971765z m-336.112941-184.32l-10.842353 68.668235h138.541176l12.047059-68.668235h-139.745882z m1187.779764-115.651765l-57.825882 427.670589c-1.626353 12.047059-5.662118 22.889412-12.047059 32.527058-6.445176 9.637647-14.456471 18.070588-24.094117 25.298824-8.854588 7.228235-18.492235 12.890353-28.912942 16.865882-9.637647 4.035765-18.913882 6.023529-27.708235 6.02353h-287.924706l-39.755294 252.988235h-231.303529l107.218823-726.437647-33.731765-34.936471h636.084706z m-381.891764 115.651765l-43.369412 287.924706h139.745882l42.164706-287.924706h-138.541176z m942.019764 95.171765h-412.009411l15.661176-114.447059h412.009412l22.889412-160.225882h-457.788236l15.661177-113.242353h686.682353l-85.534118 603.557647a93.063529 93.063529 0 0 1-32.527059 59.030588c-17.709176 15.299765-37.767529 22.889412-60.235294 22.889412h-604.762353l16.865883-114.447059h456.583529l26.503529-183.115294z m443.392-210.82353h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z m-42.164706 369.844706h183.115295l-19.275295 137.336471h-183.115294l19.275294-137.336471z" p-id="100619" fill="#c5bd18"></path></svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
assets/step_4.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742360303591" class="icon" viewBox="0 0 4096 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="101799" width="200" height="200"><path d="M496.519529 583.740235l22.889412-138.541176H56.801882l57.825883-404.781177h681.863529l-22.889412 196.367059h-231.303529l12.047059-80.715294h-220.461177l-34.93647 173.477647h462.607059l-45.778824 312.018824c0 11.264-2.770824 22.106353-8.432941 32.527058-4.818824 9.637647-11.625412 18.070588-20.48 25.298824a100.592941 100.592941 0 0 1-30.117647 16.865882c-10.420706 4.035765-21.684706 6.023529-33.731765 6.02353H21.865412l22.889412-185.524706 34.93647 46.983529h416.828235z m518.505412-439.717647l-22.889412-34.93647h254.192942l-15.661177 104.809411h143.36l-24.094118 115.651765h-133.722352l-39.755295 277.082353h115.651765l-22.889412 115.651765h-254.192941c-23.250824 0-42.164706-7.589647-56.621176-22.889412-14.456471-15.239529-18.853647-34.514824-13.251765-57.825882l46.983529-312.018824h-92.762353l12.047059-115.651765h93.967059l9.637647-69.872941z m1044.600471 369.844706h-369.844706l-12.047059 92.762353h324.065882l57.825883-45.778823-34.936471 161.430588h-508.385882c-23.250824 0-40.96-7.589647-53.007059-22.889412-11.203765-15.239529-16.865882-34.514824-16.865882-57.825882l57.825882-427.670589h601.148235l-45.778823 299.971765z m-336.112941-184.32l-10.842353 68.668235h138.541176l12.047059-68.668235h-139.745882z m1187.84-115.651765l-57.825883 427.670589c-1.626353 12.047059-5.662118 22.889412-12.047059 32.527058-6.445176 9.637647-14.456471 18.070588-24.094117 25.298824-8.854588 7.228235-18.492235 12.890353-28.912941 16.865882-9.637647 4.035765-18.913882 6.023529-27.708236 6.02353h-287.924706l-39.755294 252.988235h-231.303529l107.218823-726.437647-33.731764-34.936471h636.084706z m-381.891765 115.651765l-43.369412 287.924706h139.745882l42.164706-287.924706h-138.541176zM3112.357647 36.803765h262.625882l-125.289411 433.694117h178.29647l55.416471-389.12-39.755294-44.574117h274.672941l-61.44 433.694117h91.557647l-15.661177 114.447059h-91.557647l-19.275294 137.336471h-228.894117l19.275294-137.336471h-410.804706l15.661176-114.447059 121.675294-404.781176-26.503529-28.912941zM3897.223529 213.895529h183.115295l-19.275295 137.336471h-183.115294l19.275294-137.336471z m-42.164705 369.844706h183.115294l-19.275294 137.336471h-183.115295l19.275295-137.336471z" p-id="101800" fill="#106415"></path></svg>
|
After Width: | Height: | Size: 2.3 KiB |
1
assets/step_5.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742371641172" class="icon" viewBox="0 0 4156 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="102906" width="200" height="200"><path d="M497.242353 583.740235l22.889412-138.541176H57.524706l57.825882-404.781177h681.86353l-22.889412 196.367059h-231.30353l12.047059-80.715294h-220.461176l-34.936471 173.477647h462.607059l-45.778823 312.018824c0 11.264-2.831059 22.106353-8.432942 32.527058-4.818824 9.637647-11.625412 18.070588-20.48 25.298824a100.592941 100.592941 0 0 1-30.117647 16.865882c-10.420706 4.035765-21.684706 6.023529-33.731764 6.02353H22.588235l22.889412-185.524706 34.936471 46.983529h416.828235z m518.505412-439.717647l-22.889412-34.93647h254.192941l-15.661176 104.809411h143.36l-24.094118 115.651765h-133.722353l-39.755294 277.082353h115.651765l-22.889412 115.651765h-254.192941c-23.311059 0-42.164706-7.589647-56.621177-22.889412-14.456471-15.239529-18.853647-34.514824-13.251764-57.825882l46.983529-312.018824h-92.762353l12.047059-115.651765h93.967059l9.637647-69.872941z m1044.60047 369.844706h-369.844706l-12.047058 92.762353h324.065882l57.825882-45.778823-34.93647 161.430588h-508.385883c-23.311059 0-40.96-7.589647-53.007058-22.889412-11.264-15.239529-16.865882-34.514824-16.865883-57.825882l57.825883-427.670589h601.148235l-45.778824 299.971765z m-336.112941-184.32l-10.842353 68.668235h138.541177l12.047058-68.668235h-139.745882z m1187.779765-115.651765l-57.825883 427.670589c-1.626353 12.047059-5.601882 22.889412-12.047058 32.527058-6.445176 9.637647-14.456471 18.070588-24.094118 25.298824-8.854588 7.228235-18.492235 12.890353-28.912941 16.865882-9.637647 4.035765-18.853647 6.023529-27.708235 6.02353h-287.924706l-39.755294 252.988235h-231.30353l107.218824-726.437647-33.731765-34.936471h636.084706z m-381.891765 115.651765l-43.369412 287.924706h139.745883l42.164706-287.924706h-138.541177z m956.476235 278.287059l30.117647-206.004706h-457.788235l45.778824-321.656471-39.755294-40.96h731.25647l-15.661176 110.832942h-456.58353l-20.48 137.33647h457.788236l-44.574118 320.451765-4.818824 32.527059a100.954353 100.954353 0 0 1-32.527058 59.030588c-17.648941 15.299765-38.128941 22.889412-61.44 22.889412h-603.557647l21.684705-160.225883 39.755295 45.778824h410.804705z m470.136471-393.938824h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z m-42.164706 369.844706h183.115294l-19.275294 137.336471h-183.115294l19.275294-137.336471z" fill="#21BB9E" p-id="102907"></path></svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
assets/style.svg
Normal file
After Width: | Height: | Size: 6.7 KiB |
1
assets/theme.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1742376669137" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="118779" width="200" height="200"><path d="M511.887385 0.222671C138.27358 0.222671 0 327.126752 0 510.446422S132.898762 1023.997441 501.009778 1023.997441c0 0 91.49987 1.599648 91.49987-80.942193s-41.142949-56.17964-41.142949-115.558577a78.702685 78.702685 0 0 1 60.914599-85.869109 680.746236 680.746236 0 0 0 222.223111-18.235988A296.638739 296.638739 0 0 0 1023.77477 454.522725 499.0902 499.0902 0 0 0 511.887385 0.222671zM199.828038 512.110056a89.196377 89.196377 0 1 1 88.876447-89.196377A89.004419 89.004419 0 0 1 199.828038 512.110056z m167.643118-220.687449a89.196377 89.196377 0 1 1 88.876448-89.196377 89.004419 89.004419 0 0 1-88.876448 89.260363z m285.057288 0a89.196377 89.196377 0 1 1 88.876447-89.196377A89.068405 89.068405 0 0 1 652.656416 291.486593zM821.77121 512.110056a89.196377 89.196377 0 1 1 88.876448-89.196377A89.068405 89.068405 0 0 1 821.77121 512.110056z" fill="#FAD263" p-id="118780"></path><path d="M821.77121 512.110056a89.196377 89.196377 0 1 1 88.876448-89.196377A89.068405 89.068405 0 0 1 821.77121 512.110056z" fill="#33A0BF" p-id="118781"></path><path d="M652.656416 291.486593a89.196377 89.196377 0 1 1 88.876447-89.196377A89.068405 89.068405 0 0 1 652.656416 291.486593z" fill="#96BE2A" p-id="118782"></path><path d="M367.471156 291.486593a89.196377 89.196377 0 1 1 88.876448-89.196377 89.004419 89.004419 0 0 1-88.876448 89.196377z" fill="#F26D5C" p-id="118783"></path><path d="M199.828038 512.110056a89.196377 89.196377 0 1 1 88.876447-89.196377A89.004419 89.004419 0 0 1 199.828038 512.110056z" fill="#466381" p-id="118784"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
209
benchmark/README.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# <div align="center">🚀 PixelNebula 基准测试</div>
|
||||
|
||||
|
||||
[英文](README_EN.md) | 中文
|
||||
|
||||
|
||||
## 📊 简介
|
||||
|
||||
本目录包含了 PixelNebula 库的全面基准测试,用于测量和分析库在各种操作场景下的性能表现。这些测试可以帮助我们识别潜在的性能瓶颈,并指导后续的优化工作。
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🧪 测试内容
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/example_avatar.svg" width="80" height="80" alt="基本生成" /><br/>
|
||||
<strong>基本头像生成</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/style.svg" width="80" height="80" alt="样式和主题" /><br/>
|
||||
<strong>样式和主题</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/animation.svg" width="80" height="80" alt="动画效果" /><br/>
|
||||
<strong>动画效果</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/cache.svg" width="80" height="80" alt="缓存系统" /><br/>
|
||||
<strong>缓存系统</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/performance.svg" width="80" height="80" alt="并发性能" /><br/>
|
||||
<strong>并发与内存</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
基准测试涵盖了 PixelNebula 的以下核心功能:
|
||||
|
||||
### 1. 🖼️ 基本头像生成 (`basic_benchmark_test.go`)
|
||||
- 普通头像生成
|
||||
- 无环境头像生成
|
||||
- 不同尺寸头像生成
|
||||
- 相同ID多次生成
|
||||
|
||||
### 2. 🎨 样式和主题 (`style_theme_benchmark_test.go`)
|
||||
- 不同样式的性能对比
|
||||
- 不同主题的性能对比
|
||||
- 自定义主题
|
||||
- 样式与主题组合
|
||||
|
||||
### 3. ✨ 动画效果 (`animation_benchmark_test.go`)
|
||||
- 旋转动画
|
||||
- 渐变动画
|
||||
- 淡入淡出动画
|
||||
- 变换动画
|
||||
- 颜色动画
|
||||
- 多个动画组合
|
||||
|
||||
### 4. 💾 缓存系统 (`cache_benchmark_test.go`)
|
||||
- 无缓存 vs. 默认缓存
|
||||
- 不同缓存大小
|
||||
- 缓存压缩效果
|
||||
- 不同过期时间配置
|
||||
|
||||
### 5. ⚡ 并发与内存使用 (`concurrency_memory_benchmark_test.go`)
|
||||
- 不同并发级别的性能
|
||||
- 共享实例的并发性能
|
||||
- 各种操作的内存占用分析
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🚀 运行基准测试
|
||||
|
||||
### 运行所有测试
|
||||
|
||||
```bash
|
||||
cd benchmark
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
### 运行特定测试组
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>🏃 基本测试</h4>
|
||||
<pre>go test -bench=BenchmarkBasic -benchmem</pre>
|
||||
</div>
|
||||
</td>
|
||||
<td width="33%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>💾 缓存测试</h4>
|
||||
<pre>go test -bench=BenchmarkCache -benchmem</pre>
|
||||
</div>
|
||||
</td>
|
||||
<td width="33%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>✨ 动画测试</h4>
|
||||
<pre>go test -bench=BenchmarkAnimation -benchmem</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### 高级配置
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>⚙️ 设置CPU计数</h4>
|
||||
<pre>go test -bench=. -benchmem -cpu=1,2,4,8</pre>
|
||||
</div>
|
||||
</td>
|
||||
<td width="50%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>⏱️ 设置迭代次数和时间</h4>
|
||||
<pre>go test -bench=. -benchmem -count=5 -benchtime=5s</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📈 测试结果分析
|
||||
|
||||
运行测试后,结果会以如下格式显示:
|
||||
|
||||
```
|
||||
BenchmarkBasicAvatarGeneration-8 5000 234567 ns/op 12345 B/op 123 allocs/op
|
||||
```
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th align="center">组成部分</th>
|
||||
<th align="center">描述</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>BenchmarkBasicAvatarGeneration-8</code></td>
|
||||
<td>测试名称,8表示使用8个CPU</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>5000</code></td>
|
||||
<td>测试运行的迭代次数</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>234567 ns/op</code></td>
|
||||
<td>每次操作的平均耗时(纳秒)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>12345 B/op</code></td>
|
||||
<td>每次操作的平均内存分配(字节)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>123 allocs/op</code></td>
|
||||
<td>每次操作的平均内存分配次数</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📝 添加基准测试
|
||||
|
||||
在添加新功能时,建议同时添加相应的基准测试:
|
||||
|
||||
1. **创建测试函数**:为特定功能创建新的测试函数,命名为 `BenchmarkXXX`
|
||||
2. **重置计时器**:使用 `b.ResetTimer()` 在准备工作完成后重置计时器
|
||||
3. **创建子测试**:使用 `b.Run()` 创建子测试以对比不同变量
|
||||
4. **使用迭代计数**:使用 `b.N` 作为迭代次数以确保统计准确性
|
||||
|
||||
<div>
|
||||
<p><strong>示例:</strong></p>
|
||||
|
||||
```go
|
||||
func BenchmarkMyFeature(b *testing.B) {
|
||||
// 准备代码
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 在实际基准测试前重置计时器
|
||||
b.ResetTimer()
|
||||
|
||||
// 运行基准测试
|
||||
for i := 0; i < b.N; i++ {
|
||||
// 要测试的代码
|
||||
pn.MyFeature()
|
||||
}
|
||||
}
|
||||
```
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div align="center">
|
||||
<p><em>更多信息,请查看 PixelNebula 文档和示例。</em></p>
|
||||
<p>© 2024 landaiqing</p>
|
||||
</div>
|
209
benchmark/README_EN.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# <div align="center">🚀 PixelNebula Benchmarks</div>
|
||||
|
||||
|
||||
[中文](README.md) | English
|
||||
|
||||
|
||||
## 📊 Introduction
|
||||
|
||||
This directory contains comprehensive benchmark tests for the PixelNebula library, designed to measure and analyze performance across various operational scenarios. These tests help identify potential performance bottlenecks and guide subsequent optimization efforts.
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🧪 Test Content
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/example_avatar.svg" width="80" height="80" alt="Basic Generation" /><br/>
|
||||
<strong>Basic Avatar Generation</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/style.svg" width="80" height="80" alt="Styles & Themes" /><br/>
|
||||
<strong>Styles & Themes</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/animation.svg" width="80" height="80" alt="Animations" /><br/>
|
||||
<strong>Animation Effects</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/cache.svg" width="80" height="80" alt="Cache System" /><br/>
|
||||
<strong>Cache System</strong>
|
||||
</td>
|
||||
<td align="center" width="20%">
|
||||
<img src="../assets/performance.svg" width="80" height="80" alt="Concurrency" /><br/>
|
||||
<strong>Concurrency & Memory</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
The benchmarks cover the following core functionalities of PixelNebula:
|
||||
|
||||
### 1. 🖼️ Basic Avatar Generation (`basic_benchmark_test.go`)
|
||||
- Regular avatar generation
|
||||
- No-environment avatar generation
|
||||
- Avatar generation with different sizes
|
||||
- Multiple generations with the same ID
|
||||
|
||||
### 2. 🎨 Styles and Themes (`style_theme_benchmark_test.go`)
|
||||
- Performance comparison between different styles
|
||||
- Performance comparison between different themes
|
||||
- Custom themes
|
||||
- Style and theme combinations
|
||||
|
||||
### 3. ✨ Animation Effects (`animation_benchmark_test.go`)
|
||||
- Rotation animation
|
||||
- Gradient animation
|
||||
- Fade-in/out animation
|
||||
- Transform animation
|
||||
- Color animation
|
||||
- Multiple animation combinations
|
||||
|
||||
### 4. 💾 Cache System (`cache_benchmark_test.go`)
|
||||
- No cache vs. default cache
|
||||
- Different cache sizes
|
||||
- Cache compression effects
|
||||
- Different expiry time configurations
|
||||
|
||||
### 5. ⚡ Concurrency and Memory Usage (`concurrency_memory_benchmark_test.go`)
|
||||
- Performance at different concurrency levels
|
||||
- Concurrent performance with shared instances
|
||||
- Memory usage analysis for various operations
|
||||
|
||||
<hr/>
|
||||
|
||||
## 🚀 Running Benchmarks
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
cd benchmark
|
||||
go test -bench=. -benchmem
|
||||
```
|
||||
|
||||
### Run Specific Test Groups
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>🏃 Basic Tests</h4>
|
||||
<pre>go test -bench=BenchmarkBasic -benchmem</pre>
|
||||
</div>
|
||||
</td>
|
||||
<td width="33%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>💾 Cache Tests</h4>
|
||||
<pre>go test -bench=BenchmarkCache -benchmem</pre>
|
||||
</div>
|
||||
</td>
|
||||
<td width="33%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>✨ Animation Tests</h4>
|
||||
<pre>go test -bench=BenchmarkAnimation -benchmem</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>⚙️ Set CPU Count</h4>
|
||||
<pre>go test -bench=. -benchmem -cpu=1,2,4,8</pre>
|
||||
</div>
|
||||
</td>
|
||||
<td width="50%" align="center">
|
||||
<div style="padding: 15px; border-radius: 10px;">
|
||||
<h4>⏱️ Set Iteration Count and Duration</h4>
|
||||
<pre>go test -bench=. -benchmem -count=5 -benchtime=5s</pre>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📈 Test Result Analysis
|
||||
|
||||
After running the tests, results will be displayed in the following format:
|
||||
|
||||
```
|
||||
BenchmarkBasicAvatarGeneration-8 5000 234567 ns/op 12345 B/op 123 allocs/op
|
||||
```
|
||||
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<th align="center">Component</th>
|
||||
<th align="center">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>BenchmarkBasicAvatarGeneration-8</code></td>
|
||||
<td>Test name, 8 indicates using 8 CPUs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>5000</code></td>
|
||||
<td>Number of iterations the test ran</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>234567 ns/op</code></td>
|
||||
<td>Average time per operation (nanoseconds)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>12345 B/op</code></td>
|
||||
<td>Average memory allocation per operation (bytes)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><code>123 allocs/op</code></td>
|
||||
<td>Average number of memory allocations per operation</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
## 📝 Adding Benchmark Tests
|
||||
|
||||
When adding new features, it's recommended to add corresponding benchmark tests:
|
||||
|
||||
1. **Create a Test Function**: Create a new test function for the specific feature, named `BenchmarkXXX`
|
||||
2. **Reset Timer**: Use `b.ResetTimer()` to reset the timer after setup work is complete
|
||||
3. **Create Sub-tests**: Use `b.Run()` to create sub-tests for comparing different variables
|
||||
4. **Use Iteration Count**: Use `b.N` as the iteration count to ensure statistical accuracy
|
||||
|
||||
<div>
|
||||
<p><strong>Example:</strong></p>
|
||||
|
||||
```go
|
||||
func BenchmarkMyFeature(b *testing.B) {
|
||||
// Setup code
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// Reset timer before the actual benchmark
|
||||
b.ResetTimer()
|
||||
|
||||
// Run the benchmark
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Code to benchmark
|
||||
pn.MyFeature()
|
||||
}
|
||||
}
|
||||
```
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div align="center">
|
||||
<p><em>For more information, check out the PixelNebula documentation and examples.</em></p>
|
||||
<p>© 2024 landaiqing</p>
|
||||
</div>
|
132
benchmark/animation_benchmark_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// BenchmarkRotateAnimation 测试旋转动画的性能
|
||||
func BenchmarkRotateAnimation(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, 1) // 单次旋转
|
||||
|
||||
_, err := pn.Generate("benchmark-rotate", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkGradientAnimation 测试渐变动画的性能
|
||||
func BenchmarkGradientAnimation(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithGradientAnimation("head", []string{"#ff0000", "#00ff00", "#0000ff"}, 5, 1, true)
|
||||
|
||||
_, err := pn.Generate("benchmark-gradient", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkFadeAnimation 测试淡入淡出动画的性能
|
||||
func BenchmarkFadeAnimation(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, 1)
|
||||
|
||||
_, err := pn.Generate("benchmark-fade", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTransformAnimation 测试变换动画的性能
|
||||
func BenchmarkTransformAnimation(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithTransformAnimation("mouth", "scale", "1 1", "1.1 1.1", 1.5, 1)
|
||||
|
||||
_, err := pn.Generate("benchmark-transform", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkColorAnimation 测试颜色变换动画的性能
|
||||
func BenchmarkColorAnimation(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithColorAnimation("clo", "fill", "#ff0000", "#0000ff", 3, 1)
|
||||
|
||||
_, err := pn.Generate("benchmark-color", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMultipleAnimations 测试多个动画组合的性能
|
||||
func BenchmarkMultipleAnimations(b *testing.B) {
|
||||
animationCounts := []int{1, 2, 3, 5}
|
||||
|
||||
for _, count := range animationCounts {
|
||||
b.Run("Animations_"+Itoa(count), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
// 根据数量添加不同的动画
|
||||
if count >= 1 {
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, 1)
|
||||
}
|
||||
if count >= 2 {
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, 1)
|
||||
}
|
||||
if count >= 3 {
|
||||
pn.WithTransformAnimation("mouth", "scale", "1 1", "1.1 1.1", 1.5, 1)
|
||||
}
|
||||
if count >= 4 {
|
||||
pn.WithColorAnimation("clo", "fill", "#ff0000", "#0000ff", 3, 1)
|
||||
}
|
||||
if count >= 5 {
|
||||
pn.WithGradientAnimation("head", []string{"#ff0000", "#00ff00", "#0000ff"}, 5, 1, true)
|
||||
}
|
||||
|
||||
_, err := pn.Generate("benchmark-multi-"+Itoa(count), false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
88
benchmark/basic_benchmark_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// BenchmarkBasicAvatarGeneration 测试基本头像生成性能
|
||||
func BenchmarkBasicAvatarGeneration(b *testing.B) {
|
||||
// 重置计时器
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
// 生成SVG
|
||||
_, err := pn.Generate("benchmark-id", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkAvatarWithNoEnvironment 测试无环境头像生成性能
|
||||
func BenchmarkAvatarWithNoEnvironment(b *testing.B) {
|
||||
// 重置计时器
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
// 生成无环境SVG
|
||||
_, err := pn.Generate("benchmark-id", true).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDifferentSizes 测试不同大小头像生成性能
|
||||
func BenchmarkDifferentSizes(b *testing.B) {
|
||||
sizes := []int{100, 200, 400, 800}
|
||||
|
||||
for _, size := range sizes {
|
||||
b.Run("Size_"+Itoa(size), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(size, size)
|
||||
|
||||
_, err := pn.Generate("benchmark-size-"+Itoa(size), false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIDReuse 测试多次使用相同ID生成头像的性能(不使用缓存)
|
||||
func BenchmarkIDReuse(b *testing.B) {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := pn.Generate("fixed-benchmark-id", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Itoa 简单的整数转字符串函数
|
||||
func Itoa(n int) string {
|
||||
return strconv.Itoa(n)
|
||||
}
|
155
benchmark/cache_benchmark_test.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/cache"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// BenchmarkDefaultCacheVsNoCache 对比有无默认缓存的性能差异
|
||||
func BenchmarkDefaultCacheVsNoCache(b *testing.B) {
|
||||
// 不使用缓存的基准测试
|
||||
b.Run("NoCache", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
_, err := pn.Generate("benchmark-cache-test", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 使用默认缓存的基准测试
|
||||
b.Run("DefaultCache", func(b *testing.B) {
|
||||
// 创建一个带默认缓存的实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithDefaultCache()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := pn.Generate("benchmark-cache-test", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkCacheSizes 测试不同缓存大小对性能的影响
|
||||
func BenchmarkCacheSizes(b *testing.B) {
|
||||
cacheSizes := []int{10, 100, 1000}
|
||||
|
||||
for _, size := range cacheSizes {
|
||||
b.Run("CacheSize_"+Itoa(size), func(b *testing.B) {
|
||||
// 创建自定义缓存配置
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithCache(cache.CacheOptions{
|
||||
Size: size,
|
||||
Expiration: 3600 * time.Second,
|
||||
})
|
||||
|
||||
// 预热缓存,生成一些不同的头像
|
||||
for i := 0; i < size/2; i++ {
|
||||
_, _ = pn.Generate("preload-"+Itoa(i), false).ToSVG()
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
// 测试缓存命中和未命中的混合场景
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := "benchmark-" + Itoa(i%size) // 循环使用ID,确保部分缓存命中
|
||||
_, err := pn.Generate(id, false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCacheCompression 测试缓存压缩对性能的影响
|
||||
func BenchmarkCacheCompression(b *testing.B) {
|
||||
compressionLevels := []struct {
|
||||
name string
|
||||
level int
|
||||
}{
|
||||
{"NoCompression", 0},
|
||||
{"LowCompression", 3},
|
||||
{"MediumCompression", 6},
|
||||
{"HighCompression", 9},
|
||||
}
|
||||
|
||||
for _, cl := range compressionLevels {
|
||||
b.Run(cl.name, func(b *testing.B) {
|
||||
// 创建带压缩缓存的实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithDefaultCache()
|
||||
|
||||
if cl.level > 0 {
|
||||
pn.WithCompression(cache.CompressOptions{
|
||||
Enabled: true,
|
||||
Level: cl.level,
|
||||
MinSizeBytes: 100,
|
||||
})
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := pn.Generate("benchmark-compress", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCacheExpiry 测试不同缓存过期时间的性能影响
|
||||
func BenchmarkCacheExpiry(b *testing.B) {
|
||||
expiryTimes := []struct {
|
||||
name string
|
||||
time time.Duration
|
||||
}{
|
||||
{"Short_1m", 1 * time.Minute},
|
||||
{"Medium_1h", 1 * time.Hour},
|
||||
{"Long_24h", 24 * time.Hour},
|
||||
}
|
||||
|
||||
for _, et := range expiryTimes {
|
||||
b.Run(et.name, func(b *testing.B) {
|
||||
// 创建带自定义过期时间的缓存实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithCache(cache.CacheOptions{
|
||||
Size: 100,
|
||||
Expiration: et.time,
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := pn.Generate("benchmark-expiry", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
138
benchmark/concurrency_memory_benchmark_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// BenchmarkConcurrentGeneration 测试并发生成头像的性能
|
||||
func BenchmarkConcurrentGeneration(b *testing.B) {
|
||||
concurrencyCounts := []int{1, 2, 4, 8, 16}
|
||||
|
||||
for _, count := range concurrencyCounts {
|
||||
b.Run("Concurrent_"+Itoa(count), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
// 将总迭代次数调整为b.N,确保可比较性
|
||||
b.SetParallelism(count)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
counter := 0
|
||||
for pb.Next() {
|
||||
counter++
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithDefaultCache()
|
||||
|
||||
_, err := pn.Generate("benchmark-concurrent-"+Itoa(counter), false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkConcurrentWithSharedInstance 测试使用共享实例进行并发生成的性能
|
||||
func BenchmarkConcurrentWithSharedInstance(b *testing.B) {
|
||||
concurrencyCounts := []int{1, 2, 4, 8, 16}
|
||||
|
||||
for _, count := range concurrencyCounts {
|
||||
b.Run("SharedInstance_"+Itoa(count), func(b *testing.B) {
|
||||
// 创建一个共享实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithDefaultCache()
|
||||
|
||||
// 创建互斥锁保护共享实例
|
||||
var mu sync.Mutex
|
||||
|
||||
b.ResetTimer()
|
||||
b.SetParallelism(count)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
counter := 0
|
||||
for pb.Next() {
|
||||
counter++
|
||||
// 锁定共享实例
|
||||
mu.Lock()
|
||||
_, err := pn.Generate("benchmark-shared-"+Itoa(counter), false).ToSVG()
|
||||
mu.Unlock()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMemoryUsage 测试不同操作的内存使用情况
|
||||
func BenchmarkMemoryUsage(b *testing.B) {
|
||||
// 注意: 这个基准测试主要关注内存分配统计,
|
||||
// Go 的基准测试框架会自动收集并报告内存统计数据
|
||||
|
||||
// 测试基本头像生成的内存使用
|
||||
b.Run("BasicGeneration", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
_, err := pn.Generate("memory-basic", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 测试添加动画的内存使用
|
||||
b.Run("WithAnimations", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, 1)
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, 1)
|
||||
_, err := pn.Generate("memory-animations", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 测试缓存的内存使用
|
||||
b.Run("WithCache", func(b *testing.B) {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithDefaultCache()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := pn.Generate("memory-cache", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 测试大尺寸头像的内存使用
|
||||
b.Run("LargeSize", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(1000, 1000)
|
||||
_, err := pn.Generate("memory-large", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
126
benchmark/style_theme_benchmark_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
"github.com/landaiqing/go-pixelnebula/theme"
|
||||
)
|
||||
|
||||
// BenchmarkDifferentStyles 测试不同风格的生成性能
|
||||
func BenchmarkDifferentStyles(b *testing.B) {
|
||||
styles := []struct {
|
||||
name string
|
||||
style style.StyleType
|
||||
}{
|
||||
{"GirlStyle", style.GirlStyle},
|
||||
{"AteamStyle", style.AteamStyle},
|
||||
{"BlondStyle", style.BlondStyle},
|
||||
{"FirehairStyle", style.FirehairStyle},
|
||||
}
|
||||
|
||||
for _, s := range styles {
|
||||
b.Run(s.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(s.style)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
_, err := pn.Generate("benchmark-style-"+s.name, false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDifferentThemes 测试不同主题的生成性能
|
||||
func BenchmarkDifferentThemes(b *testing.B) {
|
||||
// 假设有5个内置主题索引
|
||||
themeCount := 5
|
||||
|
||||
for i := 0; i < themeCount; i++ {
|
||||
b.Run("Theme_"+Itoa(i), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for j := 0; j < b.N; j++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
_, err := pn.Generate("benchmark-theme-"+Itoa(i), false).SetTheme(i).Build().ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCustomTheme 测试自定义主题的性能
|
||||
func BenchmarkCustomTheme(b *testing.B) {
|
||||
// 创建一个自定义主题
|
||||
customTheme := []theme.Theme{
|
||||
{
|
||||
theme.ThemePart{
|
||||
"env": []string{"#f0f0f0", "#e0e0e0"},
|
||||
"head": []string{"#ffd699"},
|
||||
"eyes": []string{"#555555", "#ffffff"},
|
||||
"mouth": []string{"#ff6b6b"},
|
||||
"top": []string{"#6b5b95", "#6b5b95"},
|
||||
"clo": []string{"#88b04b"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
pn.WithCustomizeTheme(customTheme)
|
||||
|
||||
_, err := pn.Generate("benchmark-custom-theme", false).ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkStyleThemeCombinations 测试不同风格和主题组合的性能
|
||||
func BenchmarkStyleThemeCombinations(b *testing.B) {
|
||||
styles := []style.StyleType{style.GirlStyle, style.AsianStyle}
|
||||
themes := []int{0, 1, 2}
|
||||
|
||||
for _, s := range styles {
|
||||
for _, t := range themes {
|
||||
styleName := "Unknown"
|
||||
switch s {
|
||||
case style.GirlStyle:
|
||||
styleName = "Girl"
|
||||
case style.AsianStyle:
|
||||
styleName = "Asian"
|
||||
}
|
||||
|
||||
b.Run(styleName+"_Theme"+Itoa(t), func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
pn.WithStyle(s)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
_, err := pn.Generate("benchmark-combo", false).SetTheme(t).Build().ToSVG()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
313
cache/cache.go
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheOptions 缓存配置选项
|
||||
type CacheOptions struct {
|
||||
Enabled bool // 是否启用缓存
|
||||
Size int // 缓存大小,0表示无限制
|
||||
Expiration time.Duration // 缓存项过期时间,0表示永不过期
|
||||
EvictionType string // 缓存淘汰策略,支持"lru"(最近最少使用)和"fifo"(先进先出)
|
||||
Compression CompressOptions // 压缩选项
|
||||
Monitoring MonitorOptions // 监控选项
|
||||
}
|
||||
|
||||
// DefaultCacheOptions 默认缓存配置
|
||||
var DefaultCacheOptions = CacheOptions{
|
||||
Enabled: true,
|
||||
Size: 100, // 默认缓存100个SVG
|
||||
Expiration: time.Hour, // 默认缓存项过期时间为1小时
|
||||
EvictionType: "lru", // 默认使用LRU淘汰策略
|
||||
Compression: DefaultCompressOptions, // 默认压缩选项
|
||||
Monitoring: DefaultMonitorOptions, // 默认监控选项
|
||||
}
|
||||
|
||||
// CacheKey 缓存键结构
|
||||
type CacheKey struct {
|
||||
Id string
|
||||
SansEnv bool
|
||||
Theme int
|
||||
Part int
|
||||
}
|
||||
|
||||
// CacheItem 缓存项结构
|
||||
type CacheItem struct {
|
||||
SVG string // SVG内容
|
||||
Compressed []byte // 压缩后的SVG数据
|
||||
IsCompressed bool // 是否已压缩
|
||||
CreatedAt time.Time // 创建时间
|
||||
LastUsed time.Time // 最后使用时间
|
||||
}
|
||||
|
||||
// PNCache SVG缓存结构
|
||||
type PNCache struct {
|
||||
Options CacheOptions
|
||||
Items map[CacheKey]*list.Element // 存储缓存项的映射
|
||||
EvictionList *list.List // 用于实现LRU/FIFO的双向链表
|
||||
Mutex sync.RWMutex
|
||||
Hits int // 缓存命中次数
|
||||
Misses int // 缓存未命中次数
|
||||
Monitor *Monitor // 缓存监控器
|
||||
}
|
||||
|
||||
// NewCache 创建一个新的缓存实例
|
||||
func NewCache(options CacheOptions) *PNCache {
|
||||
cache := &PNCache{
|
||||
Options: options,
|
||||
Items: make(map[CacheKey]*list.Element),
|
||||
EvictionList: list.New(),
|
||||
Hits: 0,
|
||||
Misses: 0,
|
||||
}
|
||||
|
||||
// 如果启用了监控,创建并启动监控器
|
||||
if options.Monitoring.Enabled {
|
||||
cache.Monitor = NewMonitor(cache, options.Monitoring)
|
||||
cache.Monitor.Start()
|
||||
}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// NewDefaultCache 使用默认配置创建一个新的缓存实例
|
||||
func NewDefaultCache() *PNCache {
|
||||
return NewCache(DefaultCacheOptions)
|
||||
}
|
||||
|
||||
// Get 从缓存中获取SVG
|
||||
func (c *PNCache) Get(key CacheKey) (string, bool) {
|
||||
if !c.Options.Enabled {
|
||||
c.Misses++
|
||||
return "", false
|
||||
}
|
||||
|
||||
c.Mutex.Lock() // 使用写锁以便更新LRU信息
|
||||
defer c.Mutex.Unlock()
|
||||
|
||||
element, found := c.Items[key]
|
||||
if !found {
|
||||
c.Misses++
|
||||
return "", false
|
||||
}
|
||||
|
||||
// 获取缓存项
|
||||
cacheItem := element.Value.(*CacheItem)
|
||||
|
||||
// 检查是否过期
|
||||
if c.Options.Expiration > 0 {
|
||||
if time.Since(cacheItem.CreatedAt) > c.Options.Expiration {
|
||||
// 删除过期项
|
||||
c.EvictionList.Remove(element)
|
||||
delete(c.Items, key)
|
||||
c.Misses++
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// 更新LRU信息
|
||||
if c.Options.EvictionType == "lru" {
|
||||
cacheItem.LastUsed = time.Now()
|
||||
c.EvictionList.MoveToFront(element)
|
||||
}
|
||||
|
||||
c.Hits++
|
||||
|
||||
// 如果数据已压缩,需要解压缩
|
||||
if cacheItem.IsCompressed {
|
||||
svg, err := DecompressSVG(cacheItem.Compressed, true)
|
||||
if err != nil {
|
||||
// 解压失败,返回未压缩的原始数据
|
||||
return cacheItem.SVG, true
|
||||
}
|
||||
return svg, true
|
||||
}
|
||||
|
||||
return cacheItem.SVG, true
|
||||
}
|
||||
|
||||
// Set 将SVG存入缓存
|
||||
func (c *PNCache) Set(key CacheKey, svg string) {
|
||||
if !c.Options.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
c.Mutex.Lock()
|
||||
defer c.Mutex.Unlock()
|
||||
|
||||
// 尝试压缩SVG数据
|
||||
var compressed []byte
|
||||
var isCompressed bool
|
||||
|
||||
// 如果启用了压缩,尝试压缩SVG
|
||||
if c.Options.Compression.Enabled {
|
||||
// 首先优化SVG
|
||||
optimizedSVG := OptimizeSVG(svg)
|
||||
|
||||
// 然后压缩
|
||||
compressed, isCompressed = CompressSVG(optimizedSVG, c.Options.Compression)
|
||||
|
||||
// 如果压缩成功,使用优化后的SVG
|
||||
if isCompressed {
|
||||
svg = optimizedSVG
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
if element, exists := c.Items[key]; exists {
|
||||
// 更新现有项
|
||||
cacheItem := element.Value.(*CacheItem)
|
||||
cacheItem.SVG = svg
|
||||
cacheItem.Compressed = compressed
|
||||
cacheItem.IsCompressed = isCompressed
|
||||
cacheItem.LastUsed = time.Now()
|
||||
cacheItem.CreatedAt = time.Now()
|
||||
|
||||
// 如果使用LRU策略,将项移到链表前端
|
||||
if c.Options.EvictionType == "lru" {
|
||||
c.EvictionList.MoveToFront(element)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 如果达到大小限制,需要淘汰一个项
|
||||
if c.Options.Size > 0 && len(c.Items) >= c.Options.Size {
|
||||
c.evictItem()
|
||||
}
|
||||
|
||||
// 创建新的缓存项
|
||||
now := time.Now()
|
||||
cacheItem := &CacheItem{
|
||||
SVG: svg,
|
||||
Compressed: compressed,
|
||||
IsCompressed: isCompressed,
|
||||
CreatedAt: now,
|
||||
LastUsed: now,
|
||||
}
|
||||
|
||||
// 添加到链表和映射
|
||||
element := c.EvictionList.PushFront(cacheItem)
|
||||
c.Items[key] = element
|
||||
}
|
||||
|
||||
// evictItem 根据淘汰策略移除一个缓存项
|
||||
func (c *PNCache) evictItem() {
|
||||
if c.EvictionList.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取要淘汰的元素
|
||||
var element *list.Element
|
||||
switch c.Options.EvictionType {
|
||||
case "lru":
|
||||
// LRU策略:移除链表尾部元素(最近最少使用)
|
||||
element = c.EvictionList.Back()
|
||||
default:
|
||||
// 默认使用FIFO策略:移除链表尾部元素(最先添加)
|
||||
element = c.EvictionList.Back()
|
||||
}
|
||||
|
||||
if element != nil {
|
||||
// 从链表中移除
|
||||
c.EvictionList.Remove(element)
|
||||
|
||||
// 从映射中找到并删除对应的键
|
||||
for k, v := range c.Items {
|
||||
if v == element {
|
||||
delete(c.Items, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear 清空缓存
|
||||
func (c *PNCache) Clear() {
|
||||
c.Mutex.Lock()
|
||||
defer c.Mutex.Unlock()
|
||||
|
||||
c.Items = make(map[CacheKey]*list.Element)
|
||||
c.EvictionList = list.New()
|
||||
c.Hits = 0
|
||||
c.Misses = 0
|
||||
}
|
||||
|
||||
// Size 返回当前缓存项数量
|
||||
func (c *PNCache) Size() int {
|
||||
c.Mutex.RLock()
|
||||
defer c.Mutex.RUnlock()
|
||||
|
||||
return len(c.Items)
|
||||
}
|
||||
|
||||
// Stats 返回缓存统计信息
|
||||
func (c *PNCache) Stats() (Hits, Misses int, hitRate float64) {
|
||||
c.Mutex.RLock()
|
||||
defer c.Mutex.RUnlock()
|
||||
|
||||
Hits = c.Hits
|
||||
Misses = c.Misses
|
||||
total := Hits + Misses
|
||||
if total > 0 {
|
||||
hitRate = float64(Hits) / float64(total)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveExpired 移除所有过期的缓存项
|
||||
func (c *PNCache) RemoveExpired() int {
|
||||
if c.Options.Expiration <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
c.Mutex.Lock()
|
||||
defer c.Mutex.Unlock()
|
||||
|
||||
count := 0
|
||||
now := time.Now()
|
||||
|
||||
// 遍历所有缓存项,检查是否过期
|
||||
for key, element := range c.Items {
|
||||
cacheItem := element.Value.(*CacheItem)
|
||||
if now.Sub(cacheItem.CreatedAt) > c.Options.Expiration {
|
||||
// 从链表中移除
|
||||
c.EvictionList.Remove(element)
|
||||
// 从映射中删除
|
||||
delete(c.Items, key)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
// GetOptions 获取当前缓存选项
|
||||
func (c *PNCache) GetOptions() CacheOptions {
|
||||
c.Mutex.RLock()
|
||||
defer c.Mutex.RUnlock()
|
||||
|
||||
return c.Options
|
||||
}
|
||||
|
||||
// UpdateOptions 更新缓存选项
|
||||
func (c *PNCache) UpdateOptions(options CacheOptions) {
|
||||
c.Mutex.Lock()
|
||||
defer c.Mutex.Unlock()
|
||||
|
||||
// 更新选项
|
||||
c.Options = options
|
||||
|
||||
// 如果新的缓存大小小于当前项数,需要淘汰一些项
|
||||
if c.Options.Size > 0 && c.Options.Size < len(c.Items) {
|
||||
// 计算需要淘汰的项数
|
||||
toEvict := len(c.Items) - c.Options.Size
|
||||
|
||||
// 淘汰多余的项
|
||||
for i := 0; i < toEvict; i++ {
|
||||
c.evictItem()
|
||||
}
|
||||
}
|
||||
}
|
142
cache/compress.go
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CompressOptions 压缩选项
|
||||
type CompressOptions struct {
|
||||
Enabled bool // 是否启用压缩
|
||||
Level int // 压缩级别 (1-9),1为最快压缩,9为最佳压缩
|
||||
MinSizeBytes int // 最小压缩大小,小于此大小的数据不进行压缩
|
||||
Ratio float64 // 压缩比阈值,压缩后大小/原始大小,小于此值才保存压缩结果
|
||||
}
|
||||
|
||||
// DefaultCompressOptions 默认压缩选项
|
||||
var DefaultCompressOptions = CompressOptions{
|
||||
Enabled: true,
|
||||
Level: 6, // 默认压缩级别为6,平衡压缩率和性能
|
||||
MinSizeBytes: 100, // 默认最小压缩大小为100字节
|
||||
Ratio: 0.9, // 默认压缩比阈值为0.9,即至少要压缩到原始大小的90%以下才保存压缩结果
|
||||
}
|
||||
|
||||
// CompressSVG 压缩SVG数据
|
||||
// 返回压缩后的数据和是否进行了压缩
|
||||
func CompressSVG(svg string, options CompressOptions) ([]byte, bool) {
|
||||
if !options.Enabled || len(svg) < options.MinSizeBytes {
|
||||
return []byte(svg), false
|
||||
}
|
||||
|
||||
// 创建一个bytes.Buffer来存储压缩数据
|
||||
var buf bytes.Buffer
|
||||
|
||||
// 创建一个gzip.Writer,设置压缩级别
|
||||
writer, err := gzip.NewWriterLevel(&buf, options.Level)
|
||||
if err != nil {
|
||||
return []byte(svg), false
|
||||
}
|
||||
|
||||
// 写入SVG数据
|
||||
_, err = writer.Write([]byte(svg))
|
||||
if err != nil {
|
||||
return []byte(svg), false
|
||||
}
|
||||
|
||||
// 关闭writer,确保所有数据都被写入
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return []byte(svg), false
|
||||
}
|
||||
|
||||
// 获取压缩后的数据
|
||||
compressed := buf.Bytes()
|
||||
|
||||
// 计算压缩比
|
||||
ratio := float64(len(compressed)) / float64(len(svg))
|
||||
|
||||
// 如果压缩比不理想,返回原始数据
|
||||
if ratio >= options.Ratio {
|
||||
return []byte(svg), false
|
||||
}
|
||||
|
||||
return compressed, true
|
||||
}
|
||||
|
||||
// DecompressSVG 解压缩SVG数据
|
||||
func DecompressSVG(data []byte, isCompressed bool) (string, error) {
|
||||
// 如果数据未压缩,直接返回字符串
|
||||
if !isCompressed {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// 检查数据是否为gzip格式
|
||||
if !isGzipped(data) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// 创建一个gzip.Reader
|
||||
reader, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return string(data), err
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
// 读取解压缩后的数据
|
||||
decompressed, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
return string(decompressed), nil
|
||||
}
|
||||
|
||||
// isGzipped 检查数据是否为gzip格式
|
||||
func isGzipped(data []byte) bool {
|
||||
// gzip文件的魔数是0x1f 0x8b
|
||||
return len(data) > 2 && data[0] == 0x1f && data[1] == 0x8b
|
||||
}
|
||||
|
||||
// OptimizeSVG 优化SVG字符串,移除不必要的空白和注释
|
||||
func OptimizeSVG(svg string) string {
|
||||
// 移除XML注释
|
||||
svg = removeXMLComments(svg)
|
||||
|
||||
// 移除多余的空白
|
||||
svg = removeExtraWhitespace(svg)
|
||||
|
||||
return svg
|
||||
}
|
||||
|
||||
// removeXMLComments 移除XML注释
|
||||
func removeXMLComments(svg string) string {
|
||||
for {
|
||||
start := strings.Index(svg, "<!--")
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
end := strings.Index(svg[start:], "-->") + start
|
||||
if end > start {
|
||||
svg = svg[:start] + svg[end+3:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return svg
|
||||
}
|
||||
|
||||
// removeExtraWhitespace 移除多余的空白
|
||||
func removeExtraWhitespace(svg string) string {
|
||||
// 替换多个空白字符为单个空格
|
||||
svg = strings.Join(strings.Fields(svg), " ")
|
||||
|
||||
// 优化常见的SVG标签周围的空白
|
||||
svg = strings.ReplaceAll(svg, "> <", "><")
|
||||
svg = strings.ReplaceAll(svg, " />", "/>")
|
||||
svg = strings.ReplaceAll(svg, " =", "=")
|
||||
svg = strings.ReplaceAll(svg, "= ", "=")
|
||||
|
||||
return svg
|
||||
}
|
214
cache/monitor.go
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MonitorOptions 缓存监控选项
|
||||
type MonitorOptions struct {
|
||||
Enabled bool // 是否启用监控
|
||||
SampleInterval time.Duration // 采样间隔时间
|
||||
AdjustInterval time.Duration // 调整间隔时间
|
||||
MinSize int // 最小缓存大小
|
||||
MaxSize int // 最大缓存大小
|
||||
TargetHitRate float64 // 目标命中率
|
||||
SizeGrowthFactor float64 // 缓存大小增长因子
|
||||
SizeShrinkFactor float64 // 缓存大小收缩因子
|
||||
ExpirationFactor float64 // 过期时间调整因子
|
||||
}
|
||||
|
||||
// DefaultMonitorOptions 默认监控选项
|
||||
var DefaultMonitorOptions = MonitorOptions{
|
||||
Enabled: true,
|
||||
SampleInterval: time.Minute, // 每分钟采样一次
|
||||
AdjustInterval: time.Minute * 10, // 每10分钟调整一次
|
||||
MinSize: 50, // 最小缓存大小
|
||||
MaxSize: 1000, // 最大缓存大小
|
||||
TargetHitRate: 0.8, // 目标命中率80%
|
||||
SizeGrowthFactor: 1.2, // 增长20%
|
||||
SizeShrinkFactor: 0.8, // 收缩20%
|
||||
ExpirationFactor: 1.5, // 过期时间调整因子
|
||||
}
|
||||
|
||||
// CacheStats 缓存统计信息
|
||||
type CacheStats struct {
|
||||
Size int // 当前缓存大小
|
||||
Hits int // 命中次数
|
||||
Misses int // 未命中次数
|
||||
HitRate float64 // 命中率
|
||||
MemoryUsage int64 // 内存使用量(字节)
|
||||
LastAdjusted time.Time // 最后调整时间
|
||||
SamplesCount int // 样本数量
|
||||
AvgAccessTime float64 // 平均访问时间(纳秒)
|
||||
}
|
||||
|
||||
// Monitor 缓存监控器
|
||||
type Monitor struct {
|
||||
options MonitorOptions
|
||||
cache *PNCache
|
||||
stats CacheStats
|
||||
sampleHistory []CacheStats
|
||||
mutex sync.RWMutex
|
||||
stopChan chan struct{}
|
||||
isRunning bool
|
||||
}
|
||||
|
||||
// NewMonitor 创建一个新的缓存监控器
|
||||
func NewMonitor(cache *PNCache, options MonitorOptions) *Monitor {
|
||||
return &Monitor{
|
||||
options: options,
|
||||
cache: cache,
|
||||
sampleHistory: make([]CacheStats, 0, 100), // 预分配100个样本的容量
|
||||
stopChan: make(chan struct{}),
|
||||
isRunning: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动监控器
|
||||
func (m *Monitor) Start() {
|
||||
if !m.options.Enabled || m.isRunning {
|
||||
return
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
m.isRunning = true
|
||||
m.mutex.Unlock()
|
||||
|
||||
go m.monitorRoutine()
|
||||
}
|
||||
|
||||
// Stop 停止监控器
|
||||
func (m *Monitor) Stop() {
|
||||
if !m.isRunning {
|
||||
return
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
m.isRunning = false
|
||||
m.mutex.Unlock()
|
||||
|
||||
m.stopChan <- struct{}{}
|
||||
}
|
||||
|
||||
// GetStats 获取当前缓存统计信息
|
||||
func (m *Monitor) GetStats() CacheStats {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
return m.stats
|
||||
}
|
||||
|
||||
// monitorRoutine 监控例程
|
||||
func (m *Monitor) monitorRoutine() {
|
||||
sampleTicker := time.NewTicker(m.options.SampleInterval)
|
||||
adjustTicker := time.NewTicker(m.options.AdjustInterval)
|
||||
|
||||
defer sampleTicker.Stop()
|
||||
defer adjustTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
return
|
||||
case <-sampleTicker.C:
|
||||
m.collectSample()
|
||||
case <-adjustTicker.C:
|
||||
m.adjustCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectSample 收集缓存样本
|
||||
func (m *Monitor) collectSample() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// 获取缓存统计信息
|
||||
hits, misses, hitRate := m.cache.Stats()
|
||||
size := m.cache.Size()
|
||||
|
||||
// 估算内存使用量(简化计算,实际应用中可能需要更精确的方法)
|
||||
memoryUsage := int64(size * 1024) // 假设每个缓存项平均占用1KB
|
||||
|
||||
// 创建新的统计样本
|
||||
newStat := CacheStats{
|
||||
Size: size,
|
||||
Hits: hits,
|
||||
Misses: misses,
|
||||
HitRate: hitRate,
|
||||
MemoryUsage: memoryUsage,
|
||||
LastAdjusted: time.Now(),
|
||||
}
|
||||
|
||||
// 添加到历史样本
|
||||
m.sampleHistory = append(m.sampleHistory, newStat)
|
||||
|
||||
// 限制历史样本数量,保留最近的100个样本
|
||||
if len(m.sampleHistory) > 100 {
|
||||
m.sampleHistory = m.sampleHistory[len(m.sampleHistory)-100:]
|
||||
}
|
||||
|
||||
// 更新当前统计信息
|
||||
m.stats = newStat
|
||||
m.stats.SamplesCount = len(m.sampleHistory)
|
||||
}
|
||||
|
||||
// adjustCache 根据统计信息调整缓存
|
||||
func (m *Monitor) adjustCache() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// 如果样本数量不足,不进行调整
|
||||
if len(m.sampleHistory) < 5 {
|
||||
return
|
||||
}
|
||||
|
||||
// 计算平均命中率
|
||||
totalHitRate := 0.0
|
||||
for _, stat := range m.sampleHistory {
|
||||
totalHitRate += stat.HitRate
|
||||
}
|
||||
avgHitRate := totalHitRate / float64(len(m.sampleHistory))
|
||||
|
||||
// 获取当前缓存选项
|
||||
cacheOptions := m.cache.GetOptions()
|
||||
|
||||
// 根据命中率调整缓存大小
|
||||
newSize := cacheOptions.Size
|
||||
if avgHitRate < m.options.TargetHitRate {
|
||||
// 命中率低于目标,增加缓存大小
|
||||
newSize = int(float64(newSize) * m.options.SizeGrowthFactor)
|
||||
// 确保不超过最大大小
|
||||
if newSize > m.options.MaxSize {
|
||||
newSize = m.options.MaxSize
|
||||
}
|
||||
} else if avgHitRate > m.options.TargetHitRate+0.1 && m.stats.Size > m.options.MinSize {
|
||||
// 命中率远高于目标且缓存大小大于最小值,可以适当减小缓存
|
||||
newSize = int(float64(newSize) * m.options.SizeShrinkFactor)
|
||||
// 确保不小于最小大小
|
||||
if newSize < m.options.MinSize {
|
||||
newSize = m.options.MinSize
|
||||
}
|
||||
}
|
||||
|
||||
// 根据访问模式调整过期时间
|
||||
newExpiration := cacheOptions.Expiration
|
||||
if avgHitRate < m.options.TargetHitRate {
|
||||
// 命中率低,增加过期时间
|
||||
newExpiration = time.Duration(float64(newExpiration) * m.options.ExpirationFactor)
|
||||
} else if avgHitRate > m.options.TargetHitRate+0.1 {
|
||||
// 命中率高,可以适当减少过期时间
|
||||
newExpiration = time.Duration(float64(newExpiration) / m.options.ExpirationFactor)
|
||||
}
|
||||
|
||||
// 应用新的缓存选项
|
||||
if newSize != cacheOptions.Size || newExpiration != cacheOptions.Expiration {
|
||||
cacheOptions.Size = newSize
|
||||
cacheOptions.Expiration = newExpiration
|
||||
m.cache.UpdateOptions(cacheOptions)
|
||||
|
||||
// 更新最后调整时间
|
||||
m.stats.LastAdjusted = time.Now()
|
||||
}
|
||||
}
|
67
converter/converter.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
colorAttrRegex = regexp.MustCompile(`(?i)(fill|stroke):(?:#none|transparent)(?:;|\s|"|'|$)`)
|
||||
)
|
||||
|
||||
type Converter interface {
|
||||
ToBase64() (string, error)
|
||||
ToPNG() ([]byte, error)
|
||||
ToJPEG() ([]byte, error)
|
||||
}
|
||||
|
||||
type SVGConverter struct {
|
||||
svgData []byte
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
func NewSVGConverter(svgData []byte, width, height int) *SVGConverter {
|
||||
processed := preprocessSVG(svgData)
|
||||
return &SVGConverter{
|
||||
svgData: processed,
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
func preprocessSVG(data []byte) []byte {
|
||||
// 1. 移除动画元素
|
||||
data = regexp.MustCompile(`<animateTransform[^>]*>`).ReplaceAll(data, []byte{})
|
||||
|
||||
// 2. 替换 fill:#none, fill:transparent 为 fill:#000000
|
||||
processed := colorAttrRegex.ReplaceAllStringFunc(string(data), func(match string) string {
|
||||
if strings.HasPrefix(strings.ToLower(match), "fill:") {
|
||||
return "fill:#000;"
|
||||
}
|
||||
return match
|
||||
})
|
||||
return []byte(processed)
|
||||
}
|
||||
|
||||
// ToBase64 returns the SVG data as a base64-encoded string.
|
||||
func (c *SVGConverter) ToBase64() (string, error) {
|
||||
return "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString(c.svgData), nil
|
||||
}
|
||||
|
||||
// ToPNG returns the SVG data as a PNG image.
|
||||
// Note: This is not implemented yet.
|
||||
// Deprecated: It can't be perfectly implemented for the time being, so it's better to Use ToBase64 instead.
|
||||
func (c *SVGConverter) ToPNG() ([]byte, error) {
|
||||
// TODO: implement
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ToJPEG returns the SVG data as a JPEG image.
|
||||
// Note: This is not implemented yet.
|
||||
// Deprecated: It can't be perfectly implemented for the time being, so it's better to Use ToBase64 instead.
|
||||
func (c *SVGConverter) ToJPEG() ([]byte, error) {
|
||||
// TODO: implement
|
||||
return nil, nil
|
||||
}
|
15
errors/errors.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package errors
|
||||
|
||||
import "errors"
|
||||
|
||||
// 定义错误常量
|
||||
var (
|
||||
ErrAvatarIDRequired = errors.New("pixelnebula: avatar id is required")
|
||||
ErrInvalidTheme = errors.New("pixelnebula: invalid theme index")
|
||||
ErrInvalidPart = errors.New("pixelnebula: invalid part index")
|
||||
ErrInvalidShapeSetIndex = errors.New("pixelnebula: invalid shape set index")
|
||||
ErrInvalidShapeType = errors.New("pixelnebula: invalid shape type")
|
||||
ErrInvalidColor = errors.New("pixelnebula: invalid color scheme")
|
||||
ErrInsufficientHash = errors.New("pixelnebula: insufficient hash digits generated")
|
||||
ErrInvalidStyleName = errors.New("pixelnebula: invalid style name")
|
||||
)
|
59
examples/01_basic_usage.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// 基本用法示例
|
||||
// 展示如何创建简单的PixelNebula头像
|
||||
func main() {
|
||||
// 创建一个新的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 设置风格 - 这里使用默认的AfrohairStyle风格
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
|
||||
// 设置主题索引 - 每种风格有多个主题可选
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 设置头像尺寸 (宽度, 高度)
|
||||
pn.WithSize(300, 300)
|
||||
|
||||
// 生成SVG - 需要提供唯一ID和是否生成无环境模式的参数
|
||||
// 第一个参数:唯一标识符,用于生成不同的头像
|
||||
// 第二个参数:是否为无环境模式,true表示不生成背景环境
|
||||
svg, err := pn.Generate("my-unique-id-123", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile("basic_avatar.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("成功生成基本头像: basic_avatar.svg")
|
||||
|
||||
// 再生成一个无环境模式的头像
|
||||
svgNoEnv, err := pn.Generate("my-unique-id-123", true).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成无环境SVG失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile("basic_avatar_no_env.svg", []byte(svgNoEnv), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("成功生成无环境头像: basic_avatar_no_env.svg")
|
||||
}
|
83
examples/02_styles_and_themes.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// 风格和主题示例
|
||||
// 展示如何使用不同的风格和主题生成多个头像
|
||||
func main() {
|
||||
// 创建一个新的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 定义要展示的风格数组
|
||||
styles := []style.StyleType{
|
||||
style.AteamStyle,
|
||||
style.GirlStyle,
|
||||
style.CountryStyle,
|
||||
style.GeeknotStyle,
|
||||
style.PunkStyle,
|
||||
// 可以添加更多内置风格
|
||||
}
|
||||
|
||||
// 为每种风格生成不同主题的头像
|
||||
for styleIndex, styleType := range styles {
|
||||
// 设置当前风格
|
||||
pn.WithStyle(styleType)
|
||||
|
||||
// 获取风格名称用于文件命名
|
||||
var styleName string
|
||||
switch styleType {
|
||||
case style.AteamStyle:
|
||||
styleName = "ateam"
|
||||
case style.GirlStyle:
|
||||
styleName = "girl"
|
||||
case style.CountryStyle:
|
||||
styleName = "country"
|
||||
case style.GeeknotStyle:
|
||||
styleName = "geeknot"
|
||||
case style.PunkStyle:
|
||||
styleName = "punk"
|
||||
default:
|
||||
styleName = "unknown"
|
||||
}
|
||||
|
||||
// 对每种风格,生成3个不同主题的头像
|
||||
for themeIndex := 0; themeIndex < 3; themeIndex++ {
|
||||
// 设置主题
|
||||
pn.WithTheme(themeIndex)
|
||||
|
||||
// 设置尺寸
|
||||
pn.WithSize(200, 200)
|
||||
|
||||
// 生成唯一ID - 这里使用风格和主题索引组合
|
||||
uniqueID := "style-" + strconv.Itoa(styleIndex) + "-theme-" + strconv.Itoa(themeIndex)
|
||||
|
||||
// 生成SVG
|
||||
svg, err := pn.Generate(uniqueID, false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成风格%s主题%d的SVG失败: %v\n", styleName, themeIndex, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 文件名
|
||||
filename := fmt.Sprintf("%s_theme_%d.svg", styleName, themeIndex)
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile(filename, []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件%s失败: %v\n", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("成功生成头像: %s\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("所有风格和主题头像生成完成!")
|
||||
}
|
120
examples/03_custom_theme_and_style.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
"github.com/landaiqing/go-pixelnebula/theme"
|
||||
)
|
||||
|
||||
// 自定义主题和风格示例
|
||||
// 展示如何创建自定义主题和风格
|
||||
func main() {
|
||||
// 创建一个新的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 1. 自定义主题示例
|
||||
// 创建自定义主题 - 每个主题包含各部分的颜色设置
|
||||
customThemes := []theme.Theme{
|
||||
{
|
||||
theme.ThemePart{
|
||||
// 环境部分颜色
|
||||
"env": []string{"#FF5733", "#C70039"},
|
||||
// 头部颜色
|
||||
"head": []string{"#FFC300", "#FF5733"},
|
||||
// 衣服颜色
|
||||
"clo": []string{"#2E86C1", "#1A5276"},
|
||||
// 眼睛颜色
|
||||
"eyes": []string{"#000000", "#FFFFFF"},
|
||||
// 嘴巴颜色
|
||||
"mouth": []string{"#E74C3C"},
|
||||
// 头顶装饰颜色
|
||||
"top": []string{"#884EA0", "#7D3C98"},
|
||||
},
|
||||
theme.ThemePart{
|
||||
// 另一个主题配色
|
||||
"env": []string{"#3498DB", "#2874A6"},
|
||||
"head": []string{"#F5CBA7", "#F0B27A"},
|
||||
"clo": []string{"#27AE60", "#196F3D"},
|
||||
"eyes": []string{"#2C3E50", "#FDFEFE"},
|
||||
"mouth": []string{"#CB4335"},
|
||||
"top": []string{"#D35400", "#BA4A00"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 应用自定义主题
|
||||
pn.WithCustomizeTheme(customThemes)
|
||||
|
||||
// 生成使用自定义主题的头像
|
||||
pn.WithSize(250, 250)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 生成第一个自定义主题的头像
|
||||
svg1, err := pn.Generate("custom-theme-1", false).SetTheme(0).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成自定义主题1的SVG失败: %v\n", err)
|
||||
} else {
|
||||
// 保存到文件
|
||||
err = os.WriteFile("custom_theme_1.svg", []byte(svg1), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存自定义主题1文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成自定义主题1头像: custom_theme_1.svg")
|
||||
}
|
||||
}
|
||||
|
||||
// 生成第二个自定义主题的头像
|
||||
svg2, err := pn.Generate("custom-theme-2", false).SetTheme(1).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成自定义主题2的SVG失败: %v\n", err)
|
||||
} else {
|
||||
// 保存到文件
|
||||
err = os.WriteFile("custom_theme_2.svg", []byte(svg2), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存自定义主题2文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成自定义主题2头像: custom_theme_2.svg")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 自定义风格示例
|
||||
// 创建一个新的PixelNebula实例,用于自定义风格
|
||||
pn2 := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 创建自定义风格 - 每种风格包含不同形状部件的SVG路径
|
||||
// 注意:这里仅作示例,实际使用中需要提供完整的SVG路径数据
|
||||
customStyles := []style.StyleSet{
|
||||
{
|
||||
// 第一种自定义风格
|
||||
style.TypeEnv: `<circle cx="50%" cy="50%" r="48%" fill="#FILL0;"></circle>`,
|
||||
style.TypeHead: `<circle cx="50%" cy="50%" r="35%" fill="#FILL0;"></circle>`,
|
||||
style.TypeClo: `<rect x="25%" y="65%" width="50%" height="30%" fill="#FILL0;"></rect>`,
|
||||
style.TypeEyes: `<circle cx="40%" cy="45%" r="5%" fill="#FILL0;"></circle><circle cx="60%" cy="45%" r="5%" fill="#FILL1;"></circle>`,
|
||||
style.TypeMouth: `<path d="M 40% 60% Q 50% 70% 60% 60%" stroke="#FILL0;" stroke-width="2" fill="none"></path>`,
|
||||
style.TypeTop: `<path d="M 30% 30% L 50% 10% L 70% 30%" stroke="#FILL0;" stroke-width="4" fill="#FILL1;"></path>`,
|
||||
},
|
||||
}
|
||||
|
||||
// 应用自定义风格
|
||||
pn2.WithCustomizeStyle(customStyles)
|
||||
pn2.WithSize(250, 250)
|
||||
|
||||
// 使用自定义风格生成头像
|
||||
svg3, err := pn2.Generate("custom-style", false).SetStyleByIndex(0).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成自定义风格的SVG失败: %v\n", err)
|
||||
} else {
|
||||
// 保存到文件
|
||||
err = os.WriteFile("custom_style.svg", []byte(svg3), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存自定义风格文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成自定义风格头像: custom_style.svg")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("自定义主题和风格示例完成!")
|
||||
}
|
68
examples/04_all_animations.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// 所有动画效果示例
|
||||
// 展示PixelNebula支持的所有动画类型
|
||||
func main() {
|
||||
// 创建一个新的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
pn.WithSize(300, 300)
|
||||
|
||||
// 1. 旋转动画 - 让环境和头部旋转
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, -1) // 无限循环旋转环境
|
||||
pn.WithRotateAnimation("head", 0, 360, 15, -1) // 无限循环旋转头部
|
||||
|
||||
// 2. 渐变动画 - 给环境添加渐变色
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
|
||||
// 3. 淡入淡出动画 - 让眼睛闪烁
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, -1)
|
||||
|
||||
// 4. 变换动画 - 让嘴巴缩放
|
||||
pn.WithTransformAnimation("mouth", "scale", "1 1", "1.2 1.2", 1, -1)
|
||||
|
||||
// 5. 颜色变换动画 - 让头顶装饰变色
|
||||
pn.WithColorAnimation("top", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
|
||||
// 6. 弹跳动画 - 让整个头像上下弹跳
|
||||
pn.WithBounceAnimation("head", "translateY", "0", "-10", 3, 5, -1)
|
||||
|
||||
// 7. 波浪动画 - 让衣服产生波浪效果
|
||||
pn.WithWaveAnimation("clo", 5, 0.2, "horizontal", 4, -1)
|
||||
|
||||
// 8. 闪烁动画 - 让头顶装饰闪烁
|
||||
pn.WithBlinkAnimation("top", 0.3, 1.0, 4, 6, -1)
|
||||
|
||||
// 9. 路径动画 - 让眼睛沿着路径移动
|
||||
pn.WithPathAnimation("eyes", "M 0,0 C 10,-10 -10,-10 0,0", 3, -1)
|
||||
|
||||
// 10. 带旋转的路径动画 - 让眼睛在移动的同时旋转
|
||||
pn.WithPathAnimationRotate("mouth", "M 0,0 C 5,5 -5,5 0,0", "auto", 4, -1)
|
||||
|
||||
// 生成SVG
|
||||
svg, err := pn.Generate("all-animations-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile("all_animations.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("成功生成包含所有动画效果的头像: all_animations.svg")
|
||||
}
|
108
examples/05_svg_builder_chain.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// SVG构建器链式调用示例
|
||||
// 展示如何使用链式调用API创建头像
|
||||
func main() {
|
||||
// 创建一个新的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula().WithDefaultCache()
|
||||
|
||||
// 示例1: 基本链式调用
|
||||
// 使用链式调用创建并保存头像
|
||||
svg1, err := pn.Generate("chain-example-1", false).
|
||||
SetStyle(style.AfrohairStyle).
|
||||
SetTheme(0).
|
||||
SetSize(200, 200).
|
||||
ToSVG()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("生成基本链式调用SVG失败: %v\n", err)
|
||||
} else {
|
||||
// 保存到文件
|
||||
err = os.WriteFile("basic_chain.svg", []byte(svg1), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存基本链式调用SVG文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成基本链式调用头像: basic_chain.svg")
|
||||
}
|
||||
}
|
||||
|
||||
// 示例2: 带动画的链式调用
|
||||
// 使用链式调用添加多种动画效果
|
||||
svg2, err := pn.Generate("chain-example-2", false).
|
||||
SetStyle(style.GirlStyle).
|
||||
SetTheme(1).
|
||||
SetSize(300, 300).
|
||||
// 添加旋转动画
|
||||
SetRotateAnimation("env", 0, 360, 10, -1).
|
||||
// 添加淡入淡出动画
|
||||
SetFadeAnimation("eyes", "1", "0.3", 2, -1).
|
||||
// 添加变换动画
|
||||
SetTransformAnimation("mouth", "scale", "1 1", "1.2 1.2", 1, -1).
|
||||
// 添加颜色变换动画
|
||||
SetColorAnimation("top", "fill", "#9b59b6", "#e74c3c", 3, -1).
|
||||
// 构建并获取SVG
|
||||
ToSVG()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("生成带动画的链式调用SVG失败: %v\n", err)
|
||||
} else {
|
||||
// 保存到文件
|
||||
err = os.WriteFile("animated_chain.svg", []byte(svg2), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存带动画的链式调用SVG文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成带动画的链式调用头像: animated_chain.svg")
|
||||
}
|
||||
}
|
||||
|
||||
// 示例3: 直接保存到文件的链式调用
|
||||
err = pn.Generate("chain-example-3", false).
|
||||
SetStyle(style.BlondStyle).
|
||||
SetTheme(2).
|
||||
SetSize(250, 250).
|
||||
// 添加波浪动画
|
||||
SetWaveAnimation("clo", 5, 0.2, "horizontal", 4, -1).
|
||||
// 添加闪烁动画
|
||||
SetBlinkAnimation("top", 0.3, 1.0, 4, 6, -1).
|
||||
// 构建并直接保存到文件
|
||||
Build().
|
||||
ToFile("direct_file_chain.svg")
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("直接保存到文件的链式调用失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成并直接保存头像到文件: direct_file_chain.svg")
|
||||
}
|
||||
|
||||
// 示例4: 转换为Base64的链式调用
|
||||
base64, err := pn.Generate("chain-example-4", false).
|
||||
SetStyle(style.BlondStyle).
|
||||
SetTheme(0).
|
||||
SetSize(200, 200).
|
||||
// 添加旋转动画
|
||||
SetRotateAnimation("head", 0, 360, 15, -1).
|
||||
// 构建并转换为Base64
|
||||
ToBase64()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("转换为Base64的链式调用失败: %v\n", err)
|
||||
} else {
|
||||
// 保存Base64编码到文件
|
||||
err = os.WriteFile("base64_avatar.txt", []byte(base64), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存Base64编码到文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成Base64编码头像并保存到文件: base64_avatar.txt")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("SVG构建器链式调用示例完成!")
|
||||
}
|
223
examples/06_cache_system.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/cache"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// 缓存系统示例
|
||||
// 展示如何使用PixelNebula的缓存功能
|
||||
func main() {
|
||||
// 1. 使用默认缓存
|
||||
fmt.Println("=== 使用默认缓存示例 ===")
|
||||
defaultCacheExample()
|
||||
|
||||
// 2. 使用自定义缓存
|
||||
fmt.Println("\n=== 使用自定义缓存示例 ===")
|
||||
customCacheExample()
|
||||
|
||||
// 3. 使用带监控的缓存
|
||||
fmt.Println("\n=== 使用带监控的缓存示例 ===")
|
||||
monitoredCacheExample()
|
||||
|
||||
// 4. 使用压缩缓存
|
||||
fmt.Println("\n=== 使用压缩缓存示例 ===")
|
||||
compressedCacheExample()
|
||||
}
|
||||
|
||||
// 使用默认缓存示例
|
||||
func defaultCacheExample() {
|
||||
// 创建一个带默认缓存的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula().WithDefaultCache()
|
||||
|
||||
// 设置基本属性
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
pn.WithSize(200, 200)
|
||||
|
||||
// 第一次生成头像 - 会存入缓存
|
||||
startTime1 := time.Now()
|
||||
_, err := pn.Generate("default-cache-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
duration1 := time.Since(startTime1)
|
||||
|
||||
// 第二次生成相同头像 - 应该从缓存中获取
|
||||
startTime2 := time.Now()
|
||||
svg2, err := pn.Generate("default-cache-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("从缓存生成SVG失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
duration2 := time.Since(startTime2)
|
||||
|
||||
// 保存第二次生成的头像
|
||||
err = os.WriteFile("default_cache.svg", []byte(svg2), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存缓存生成的SVG文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("第一次生成耗时: %v\n", duration1)
|
||||
fmt.Printf("第二次生成耗时: %v (使用缓存)\n", duration2)
|
||||
fmt.Printf("性能提升: %.2f倍\n", float64(duration1)/float64(duration2))
|
||||
fmt.Println("成功生成带默认缓存的头像: default_cache.svg")
|
||||
}
|
||||
|
||||
// 使用自定义缓存示例
|
||||
func customCacheExample() {
|
||||
// 创建自定义缓存选项
|
||||
customCacheOptions := cache.CacheOptions{
|
||||
Enabled: true,
|
||||
Size: 100, // 最大缓存条目数
|
||||
Expiration: 1 * time.Hour, // 缓存有效期
|
||||
}
|
||||
|
||||
// 创建一个带自定义缓存的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula().WithCache(customCacheOptions)
|
||||
|
||||
// 设置基本属性
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithTheme(1)
|
||||
pn.WithSize(200, 200)
|
||||
|
||||
// 第一次生成头像 - 会存入缓存
|
||||
startTime1 := time.Now()
|
||||
_, err := pn.Generate("custom-cache-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
duration1 := time.Since(startTime1)
|
||||
|
||||
// 第二次生成相同头像 - 应该从缓存中获取
|
||||
startTime2 := time.Now()
|
||||
svg2, err := pn.Generate("custom-cache-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("从缓存生成SVG失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
duration2 := time.Since(startTime2)
|
||||
|
||||
// 保存第二次生成的头像
|
||||
err = os.WriteFile("custom_cache.svg", []byte(svg2), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存缓存生成的SVG文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("第一次生成耗时: %v\n", duration1)
|
||||
fmt.Printf("第二次生成耗时: %v (使用缓存)\n", duration2)
|
||||
fmt.Printf("性能提升: %.2f倍\n", float64(duration1)/float64(duration2))
|
||||
fmt.Println("成功生成带自定义缓存的头像: custom_cache.svg")
|
||||
}
|
||||
|
||||
// 使用带监控的缓存示例
|
||||
func monitoredCacheExample() {
|
||||
// 创建带监控的缓存选项
|
||||
monitorOptions := cache.MonitorOptions{
|
||||
Enabled: true,
|
||||
SampleInterval: 5 * time.Second,
|
||||
}
|
||||
|
||||
// 创建一个带默认缓存和监控的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula().WithDefaultCache().WithMonitoring(monitorOptions)
|
||||
|
||||
// 设置基本属性
|
||||
pn.WithStyle(style.AsianStyle)
|
||||
pn.WithTheme(0)
|
||||
pn.WithSize(200, 200)
|
||||
|
||||
// 生成多个头像以展示监控效果
|
||||
for i := 0; i < 5; i++ {
|
||||
uniqueID := fmt.Sprintf("monitor-example-%d", i)
|
||||
svg, err := pn.Generate(uniqueID, false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 重复生成相同头像以测试缓存命中
|
||||
for j := 0; j < 3; j++ {
|
||||
_, err = pn.Generate(uniqueID, false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("从缓存生成SVG失败: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存最后一个头像
|
||||
if i == 4 {
|
||||
err = os.WriteFile("monitored_cache.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存带监控缓存的SVG文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成带监控缓存的头像: monitored_cache.svg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待监控报告生成
|
||||
fmt.Println("等待监控报告生成...")
|
||||
time.Sleep(6 * time.Second)
|
||||
}
|
||||
|
||||
// 使用压缩缓存示例
|
||||
func compressedCacheExample() {
|
||||
// 创建压缩选项
|
||||
compressOptions := cache.CompressOptions{
|
||||
Enabled: true,
|
||||
Level: 6,
|
||||
MinSizeBytes: 100, // 最小压缩大小 (字节)
|
||||
}
|
||||
|
||||
// 创建一个带默认缓存和压缩的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula().WithDefaultCache().WithCompression(compressOptions)
|
||||
|
||||
// 设置基本属性
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
pn.WithSize(300, 300)
|
||||
|
||||
// 添加一些动画以增加SVG大小
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, -1)
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, -1)
|
||||
pn.WithTransformAnimation("mouth", "scale", "1 1", "1.2 1.2", 1, -1)
|
||||
|
||||
// 第一次生成头像 - 会存入压缩缓存
|
||||
startTime1 := time.Now()
|
||||
_, err := pn.Generate("compress-cache-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
duration1 := time.Since(startTime1)
|
||||
|
||||
// 第二次生成相同头像 - 应该从压缩缓存中获取
|
||||
startTime2 := time.Now()
|
||||
svg2, err := pn.Generate("compress-cache-example", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("从压缩缓存生成SVG失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
duration2 := time.Since(startTime2)
|
||||
|
||||
// 保存第二次生成的头像
|
||||
err = os.WriteFile("compressed_cache.svg", []byte(svg2), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存压缩缓存的SVG文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("第一次生成耗时: %v\n", duration1)
|
||||
fmt.Printf("第二次生成耗时: %v (使用压缩缓存)\n", duration2)
|
||||
fmt.Printf("性能提升: %.2f倍\n", float64(duration1)/float64(duration2))
|
||||
fmt.Println("成功生成带压缩缓存的头像: compressed_cache.svg")
|
||||
}
|
55
examples/07_format_conversion.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
)
|
||||
|
||||
// 格式转换示例
|
||||
// 展示如何将SVG转换为其他格式
|
||||
func main() {
|
||||
// 创建一个新的PixelNebula实例
|
||||
pn := pixelnebula.NewPixelNebula()
|
||||
|
||||
// 设置基本属性
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
pn.WithSize(500, 500) // 使用较大尺寸以便转换后的图像清晰
|
||||
|
||||
// 添加一些动画效果
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, -1)
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f"}, 5, -1, true)
|
||||
|
||||
// 1. 生成并保存SVG文件
|
||||
svgData, err := pn.Generate("format-conversion", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 保存SVG文件
|
||||
err = os.WriteFile("format_conversion.svg", []byte(svgData), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存SVG文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成SVG文件: format_conversion.svg")
|
||||
}
|
||||
|
||||
// 2. 转换为Base64格式
|
||||
base64Data, err := pn.Generate("format-conversion", false).ToBase64()
|
||||
if err != nil {
|
||||
fmt.Printf("转换为Base64失败: %v\n", err)
|
||||
} else {
|
||||
// 保存Base64数据到文件
|
||||
err = os.WriteFile("format_conversion.base64.txt", []byte(base64Data), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存Base64文件失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("成功生成Base64编码文件: format_conversion.base64.txt")
|
||||
}
|
||||
}
|
||||
fmt.Println("格式转换示例完成!")
|
||||
}
|
33
examples/08_random_avatar_generator.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// 08_random_avatar_generator.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/landaiqing/go-pixelnebula"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
savePath = "random_generated_avatars.svg"
|
||||
)
|
||||
|
||||
// 随机生成头像
|
||||
// Note: 随机传入id即可随机生成头像,固定的id会生成相同的头像
|
||||
func main() {
|
||||
|
||||
// 创建随机数
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
randomInt := rand.Intn(100)
|
||||
|
||||
pixelNebula := pixelnebula.NewPixelNebula().WithDefaultCache()
|
||||
svg, err := pixelNebula.Generate(strconv.Itoa(randomInt), false).ToSVG()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// 保存图片
|
||||
os.WriteFile(savePath, []byte(svg), 0644)
|
||||
defer os.Remove(savePath)
|
||||
}
|
117
examples/README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# PixelNebula 示例
|
||||
|
||||
[英文版](README_EN.md) | 中文版
|
||||
|
||||
## 示例文件
|
||||
|
||||
这个目录包含了多个示例,展示了如何使用 PixelNebula 库的各种功能。每个示例都是独立的,你可以单独运行它们来了解特定的功能。
|
||||
|
||||
| 文件名 | 描述 |
|
||||
|--------|------|
|
||||
| [01_basic_usage.go](01_basic_usage.go) | 演示基本的头像生成,包括常规头像和无环境头像 |
|
||||
| [02_styles_and_themes.go](02_styles_and_themes.go) | 展示如何使用不同的样式和主题 |
|
||||
| [03_custom_theme_and_style.go](03_custom_theme_and_style.go) | 演示如何创建和使用自定义主题和样式 |
|
||||
| [04_all_animations.go](04_all_animations.go) | 展示所有支持的动画效果 |
|
||||
| [05_svg_builder_chain.go](05_svg_builder_chain.go) | 演示如何使用链式API生成SVG |
|
||||
| [06_cache_system.go](06_cache_system.go) | 展示缓存系统的功能,包括默认缓存、自定义缓存和监控 |
|
||||
| [07_format_conversion.go](07_format_conversion.go) | 展示如何将SVG转换为其他格式 |
|
||||
| [08_random_avatar_generator.go](08_random_avatar_generator.go) | 交互式的随机头像生成器,支持多种样式、主题和输出格式 |
|
||||
|
||||
## 如何运行示例
|
||||
|
||||
确保您已正确安装 Go 环境并设置了 GOPATH。然后,按照以下步骤运行示例:
|
||||
|
||||
### 运行单个示例
|
||||
|
||||
```bash
|
||||
# 例如,运行基本用法示例
|
||||
go run 01_basic_usage.go
|
||||
```
|
||||
|
||||
### 运行所有示例
|
||||
|
||||
```bash
|
||||
for file in *_*.go; do
|
||||
echo "🚀 运行示例: $file"
|
||||
go run $file
|
||||
echo "------------------------"
|
||||
done
|
||||
```
|
||||
|
||||
## 示例说明
|
||||
|
||||
### 01_basic_usage.go
|
||||
|
||||
这个示例展示了 PixelNebula 的基本功能,包括:
|
||||
|
||||
- 创建一个基本的头像
|
||||
- 生成一个无环境的头像
|
||||
- 处理错误和保存文件
|
||||
|
||||
### 02_styles_and_themes.go
|
||||
|
||||
这个示例展示了如何使用不同的样式和主题:
|
||||
|
||||
- 使用预定义的样式生成头像
|
||||
- 应用不同的主题
|
||||
- 组合样式和主题
|
||||
|
||||
### 03_custom_theme_and_style.go
|
||||
|
||||
这个示例展示了如何创建和使用自定义主题和样式:
|
||||
|
||||
- 创建自定义颜色主题
|
||||
- 定义自定义样式
|
||||
- 组合自定义主题和样式
|
||||
|
||||
### 04_all_animations.go
|
||||
|
||||
这个示例展示了所有支持的动画效果:
|
||||
|
||||
- 旋转动画
|
||||
- 渐变动画
|
||||
- 淡入淡出效果
|
||||
- 变换动画
|
||||
- 颜色变换
|
||||
- 弹跳效果
|
||||
- 波浪动画
|
||||
- 闪烁效果
|
||||
- 路径动画
|
||||
|
||||
### 05_svg_builder_chain.go
|
||||
|
||||
这个示例展示了如何使用链式API:
|
||||
|
||||
- 使用链式调用创建简单的SVG
|
||||
- 添加动画效果
|
||||
- 直接保存到文件
|
||||
- 转换为Base64
|
||||
|
||||
### 06_cache_system.go
|
||||
|
||||
这个示例展示了缓存系统的功能:
|
||||
|
||||
- 使用默认缓存
|
||||
- 配置自定义缓存
|
||||
- 监控缓存性能
|
||||
- 使用压缩缓存
|
||||
|
||||
### 07_format_conversion.go
|
||||
|
||||
这个示例展示了格式转换功能:
|
||||
|
||||
- 转换为Base64
|
||||
- 其他格式暂未找到完美解决方案,欢迎 PR
|
||||
|
||||
### 08_random_avatar_generator.go
|
||||
|
||||
这个示例是一个交互式的随机头像生成器:
|
||||
|
||||
- 随机生成不同样式和主题的头像
|
||||
|
||||
## 提示
|
||||
|
||||
- 每个示例文件顶部都有详细的注释,解释了该示例所展示的功能
|
||||
- 如果遇到任何问题,请检查文件中的错误处理部分
|
||||
- 生成的头像会保存在示例代码指定的位置
|
||||
- 有些示例可能需要创建目录来保存生成的文件
|
117
examples/README_EN.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# PixelNebula Examples
|
||||
|
||||
[中文版](README.md) | English
|
||||
|
||||
## Example Files
|
||||
|
||||
This directory contains multiple examples showcasing various features of the PixelNebula library. Each example is standalone and can be run separately to understand specific functionalities.
|
||||
|
||||
| Filename | Description |
|
||||
|----------|-------------|
|
||||
| [01_basic_usage.go](01_basic_usage.go) | Demonstrates basic avatar generation, including regular and no-environment avatars |
|
||||
| [02_styles_and_themes.go](02_styles_and_themes.go) | Shows how to use different styles and themes |
|
||||
| [03_custom_theme_and_style.go](03_custom_theme_and_style.go) | Demonstrates how to create and use custom themes and styles |
|
||||
| [04_all_animations.go](04_all_animations.go) | Showcases all supported animation effects |
|
||||
| [05_svg_builder_chain.go](05_svg_builder_chain.go) | Demonstrates how to use the chainable API to generate SVGs |
|
||||
| [06_cache_system.go](06_cache_system.go) | Shows the cache system functionality, including default, custom, and monitored caching |
|
||||
| [07_format_conversion.go](07_format_conversion.go) | Shows how to convert SVGs to other formats |
|
||||
| [08_random_avatar_generator.go](08_random_avatar_generator.go) | Interactive random avatar generator with support for multiple styles, themes, and output formats |
|
||||
|
||||
## How to Run Examples
|
||||
|
||||
Ensure you have Go properly installed and GOPATH set correctly. Then, follow these steps to run the examples:
|
||||
|
||||
### Run a Single Example
|
||||
|
||||
```bash
|
||||
# For example, run the basic usage example
|
||||
go run 01_basic_usage.go
|
||||
```
|
||||
|
||||
### Run All Examples
|
||||
|
||||
```bash
|
||||
for file in *_*.go; do
|
||||
echo "🚀 Running example: $file"
|
||||
go run $file
|
||||
echo "------------------------"
|
||||
done
|
||||
```
|
||||
|
||||
## Example Details
|
||||
|
||||
### 01_basic_usage.go
|
||||
|
||||
This example demonstrates the basic functionality of PixelNebula, including:
|
||||
|
||||
- Creating a basic avatar
|
||||
- Generating a no-environment avatar
|
||||
- Handling errors and saving files
|
||||
|
||||
### 02_styles_and_themes.go
|
||||
|
||||
This example shows how to use different styles and themes:
|
||||
|
||||
- Using predefined styles to generate avatars
|
||||
- Applying different themes
|
||||
- Combining styles and themes
|
||||
|
||||
### 03_custom_theme_and_style.go
|
||||
|
||||
This example shows how to create and use custom themes and styles:
|
||||
|
||||
- Creating custom color themes
|
||||
- Defining custom styles
|
||||
- Combining custom themes and styles
|
||||
|
||||
### 04_all_animations.go
|
||||
|
||||
This example showcases all supported animation effects:
|
||||
|
||||
- Rotation animation
|
||||
- Gradient animation
|
||||
- Fade-in/out effects
|
||||
- Transform animation
|
||||
- Color transformation
|
||||
- Bounce effects
|
||||
- Wave animation
|
||||
- Blink effects
|
||||
- Path animation
|
||||
|
||||
### 05_svg_builder_chain.go
|
||||
|
||||
This example shows how to use the chainable API:
|
||||
|
||||
- Using chain calls to create simple SVGs
|
||||
- Adding animation effects
|
||||
- Saving directly to file
|
||||
- Converting to Base64
|
||||
|
||||
### 06_cache_system.go
|
||||
|
||||
This example demonstrates the cache system functionality:
|
||||
|
||||
- Using the default cache
|
||||
- Configuring custom caches
|
||||
- Monitoring cache performance
|
||||
- Using compressed caching
|
||||
|
||||
### 07_format_conversion.go
|
||||
|
||||
This example shows the format conversion capabilities:
|
||||
|
||||
- Converting to Base64
|
||||
- We haven't found a perfect solution for other formats yet, please feel free to make a PR
|
||||
|
||||
### 08_random_avatar_generator.go
|
||||
|
||||
This example is an interactive random avatar generator:
|
||||
|
||||
- Randomly generating avatars with different styles and themes
|
||||
|
||||
## Tips
|
||||
|
||||
- Each example file has detailed comments at the top explaining the functionality it demonstrates
|
||||
- Check the error handling sections in the files if you encounter any issues
|
||||
- Generated avatars will be saved in the locations specified in the example code
|
||||
- Some examples may require creating directories to save generated files
|
3
go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/landaiqing/go-pixelnebula
|
||||
|
||||
go 1.24.1
|
741
pixelnebula.go
Normal file
@@ -0,0 +1,741 @@
|
||||
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: "</svg>",
|
||||
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("<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 {
|
||||
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, "<animateTransform"); start != -1 {
|
||||
if end := strings.Index(svgCode[start:], "/>"); end != -1 {
|
||||
rotateAnimationSVGs[anim.GetTargetID()] = svgCode[start : start+end+2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 处理元素,如果元素有旋转动画,则包裹在g标签中并添加animateTransform
|
||||
// 只有当不是无环境模式时才添加环境
|
||||
if !sansEnv {
|
||||
if _, hasRotate := rotateAnimations["env"]; hasRotate {
|
||||
builder.WriteString("<g style=\"transform-box: fill-box; transform-origin: center;\">\n")
|
||||
builder.WriteString(final["env"])
|
||||
// 添加animateTransform标签
|
||||
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 {
|
||||
// 如果元素有旋转动画,则包裹在g标签中
|
||||
builder.WriteString("<g style=\"transform-box: fill-box; transform-origin: center;\">\n")
|
||||
builder.WriteString(final[elem])
|
||||
|
||||
// 添加animateTransform标签
|
||||
if animSVG, ok := rotateAnimationSVGs[elem]; ok {
|
||||
// 提取animateTransform标签部分
|
||||
if start := strings.Index(animSVG, "<animateTransform"); start != -1 {
|
||||
if end := strings.Index(animSVG[start:], "/>"); end != -1 {
|
||||
builder.WriteString(animSVG[start : start+end+2])
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.WriteString("</g>\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
|
||||
}
|
236
pixelnebula_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package pixelnebula
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/landaiqing/go-pixelnebula/style"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPixelNebula(t *testing.T) {
|
||||
pn := NewPixelNebula()
|
||||
numRegex := regexp.MustCompile(`[0-9]`)
|
||||
|
||||
// 测试多个不同的ID
|
||||
testIDs := []string{
|
||||
"example_avatar0",
|
||||
"example_avatar1",
|
||||
"example_avatar2",
|
||||
"example_avatar3",
|
||||
"example_avatar4",
|
||||
}
|
||||
|
||||
// 打印可用的风格和主题数量
|
||||
fmt.Printf("总风格数量: %d\n", pn.themeManager.StyleCount())
|
||||
for i := 0; i < pn.themeManager.StyleCount(); i++ {
|
||||
fmt.Printf("风格 %d 的主题数量: %d\n", i, pn.themeManager.ThemeCount(i))
|
||||
}
|
||||
|
||||
for i, id := range testIDs {
|
||||
// 生成并保存头像
|
||||
builder := pn.Generate(id, false)
|
||||
svg, err := builder.ToSVG()
|
||||
if err != nil {
|
||||
t.Errorf("生成头像失败 (ID: %s): %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 保存每个头像到不同的文件
|
||||
filename := fmt.Sprintf("avatar_%d.svg", i)
|
||||
err = os.WriteFile(filename, []byte(svg), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("保存头像失败 (ID: %s): %v", id, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 打印调试信息
|
||||
pn.hasher.Reset()
|
||||
pn.hasher.Write([]byte(id))
|
||||
sum := pn.hasher.Sum(nil)
|
||||
hashStr := hex.EncodeToString(sum)
|
||||
|
||||
// 提取数字
|
||||
numbers := numRegex.FindAllString(hashStr, -1)
|
||||
hashNum := pn.hashToNum(numbers)
|
||||
|
||||
fmt.Printf("\nID: %s\n", id)
|
||||
fmt.Printf("Hash: %s\n", hashStr)
|
||||
fmt.Printf("Numbers: %v\n", numbers)
|
||||
fmt.Printf("HashNum: %d\n", hashNum)
|
||||
|
||||
// 计算并打印每个部分的索引
|
||||
parts := []string{"env", "clo", "head", "mouth", "eyes", "top"}
|
||||
for j, part := range parts {
|
||||
start := j * 2
|
||||
end := start + 2
|
||||
if end > len(numbers) {
|
||||
end = len(numbers)
|
||||
}
|
||||
partHash := numbers[start:end]
|
||||
key := pn.calcKey(partHash, nil)
|
||||
fmt.Printf("%s - StyleIndex: %d, ThemeIndex: %d\n", part, key[0], key[1])
|
||||
}
|
||||
fmt.Printf("------------------\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnimation(t *testing.T) {
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.AfrohairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 1. 旋转动画 - 让环境和头部旋转
|
||||
pn.WithRotateAnimation("env", 0, 360, 10, -1) // 无限循环旋转环境
|
||||
|
||||
// 2. 渐变动画 - 让环境渐变
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
// 2. 渐变动画 - 让眼睛渐变
|
||||
pn.WithGradientAnimation("eyes", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
|
||||
// 3. 淡入淡出动画 - 让眼睛闪烁
|
||||
pn.WithFadeAnimation("eyes", "1", "0.3", 2, -1)
|
||||
|
||||
// 4. 变换动画 - 让嘴巴缩放
|
||||
//pn.WithTransformAnimation("mouth", "scale", "1 1", "1.2 1.2", 1, -1)
|
||||
|
||||
// 5. 颜色变换动画 - 让头发颜色变换
|
||||
pn.WithColorAnimation("top", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
// 5. 颜色变换动画 - 让衣服颜色变换
|
||||
pn.WithColorAnimation("clo", "fill", "#9b59b6", "#e74c3c", 3, -1)
|
||||
|
||||
// 6. 弹跳动画 - 让嘴巴弹跳
|
||||
pn.WithBounceAnimation("mouth", "transform", "0,0", "0,-10", 5, 2.5, -1)
|
||||
// 6. 旋转动画 - 让嘴巴旋转
|
||||
pn.WithRotateAnimation("mouth", 0, 360, 10, -1) // 无限循环旋转环境
|
||||
|
||||
//// 7. 波浪动画 - 让衣服产生波浪效果
|
||||
//pn.WithWaveAnimation("clo", 5, 0.2, "horizontal", 4, -1)
|
||||
|
||||
// 8. 闪烁动画 - 让头顶装饰闪烁
|
||||
//pn.WithBlinkAnimation("head", 0.3, 1.0, 4, 6, -1)
|
||||
// 8. 波浪动画 - 让环境产生波浪效果
|
||||
//pn.WithWaveAnimation("clo", 5, 2, "horizontal", 4, -1)
|
||||
|
||||
// 9. 路径动画 - 让眼睛沿着路径移动
|
||||
//pn.WithPathAnimation("eyes", "M 0,0 C 10,-10 -10,-10 0,0", 3, -1)
|
||||
|
||||
pn.WithBounceAnimation("eyes", "transform", "0,0", "0,-5", 5, 2, -1)
|
||||
|
||||
// 10. 带旋转的路径动画 - 让眼睛在移动的同时旋转
|
||||
//pn.WithPathAnimationRotate("mouth", "M 0,0 C 5,5 -5,5 0,0", "auto", 4, -1)
|
||||
|
||||
// 生成SVG
|
||||
svg, err := pn.Generate("example_avatar", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成SVG失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile("./assets/example_avatar.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDemo(t *testing.T) {
|
||||
|
||||
// 创建一个新的 PixelNebula 实例
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.GirlStyle)
|
||||
pn.WithSize(231, 231)
|
||||
|
||||
// 生成 SVG 并保存到文件
|
||||
svg, err := pn.Generate("unique-id-123", false).ToSVG()
|
||||
if err != nil {
|
||||
fmt.Printf("生成 SVG 失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
err = os.WriteFile("my_avatar.svg", []byte(svg), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("保存文件失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("头像成功生成: my_avatar.svg")
|
||||
|
||||
}
|
||||
|
||||
func TestRotateAnimation(t *testing.T) {
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.FirehairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 1. 旋转动画 - 让环境和头部旋转
|
||||
pn.WithRotateAnimation("eyes", 0, 360, 10, -1) // 无限循环旋转环境
|
||||
|
||||
err := pn.Generate("example_avatar", false).ToFile("example_avatar.svg")
|
||||
if err != nil {
|
||||
fmt.Printf("生成 SVG 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGradientAnimation(t *testing.T) {
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.FirehairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 2. 渐变动画 - 让环境渐变
|
||||
pn.WithGradientAnimation("env", []string{"#3498db", "#2ecc71", "#f1c40f", "#e74c3c", "#9b59b6"}, 8, -1, true)
|
||||
|
||||
err := pn.Generate("example_avatar", false).ToFile("example_avatar.svg")
|
||||
if err != nil {
|
||||
fmt.Printf("生成 SVG 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试淡入淡出动画
|
||||
func TestFadeAnimation(t *testing.T) {
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.FirehairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 3. 淡入淡出动画 - 让眼睛闪烁
|
||||
pn.WithFadeAnimation("head", "1", "0.3", 2, -1)
|
||||
|
||||
err := pn.Generate("example_avatar", false).ToFile("example_avatar.svg")
|
||||
if err != nil {
|
||||
fmt.Printf("生成 SVG 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试路径动画
|
||||
func TestPathAnimation(t *testing.T) {
|
||||
pn := NewPixelNebula()
|
||||
|
||||
// 设置风格和尺寸
|
||||
pn.WithStyle(style.FirehairStyle)
|
||||
pn.WithTheme(0)
|
||||
|
||||
// 9. 路径动画 - 让clo沿着路径移动
|
||||
pn.WithPathAnimation("clo", "M 0,0 C 10,-10 -10,-10 0,0", 3, -1)
|
||||
|
||||
err := pn.Generate("example_avatar", false).ToFile("example_avatar.svg")
|
||||
if err != nil {
|
||||
fmt.Printf("生成 SVG 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
196
style/init.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package style
|
||||
|
||||
var defaultStyleSet = map[StyleType]StyleSet{
|
||||
// 创建第一组形状集合 - Robo风格
|
||||
RoboStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.74 195a114.93 114.93 0 0 1 37.912 16.45l0.07 0.05c-1.17 0.79-2.3601 1.55-3.5601 2.29a115.55 115.55 0 0 1-120.95 0.21q-2.0001-1.23-4.0002-2.54a114.79 114.79 0 0 1 38.002-16.5 116.21 116.21 0 0 1 15.791-2.49v-14.57c1.32 0.22 2.6501 0.39 4.0002 0.51 2.0001 0.19 4.0002 0.28 6.1202 0.29a64.333 64.33 0 0 0 8.8804-0.62c0.67003-0.09 1.3401-0.2 2.0001-0.31v14.69a118 118 0 0 1 15.741 2.54z\" style=\"fill:#fff;\"/><path d=\"m79.292 212a3.4601 3.46 0 0 0 3.8902 5.07 3.3801 3.38 0 0 0 2.1001-1.61 3.4701 3.47 0 0 0-1.2801-4.72 3.4201 3.42 0 0 0-2.6201-0.34 3.5101 3.51 0 0 0-2.0901 1.6zm60.122 0.46a3.4901 3.49 0 0 0 1.21 4.7h0.06a3.4601 3.46 0 0 0 4.7202-1.27l0.07-0.13a3.4601 3.46 0 0 0-1.34-4.6 3.4601 3.46 0 0 0-2.5801-0.32 3.5301 3.53 0 0 0-2.1001 1.61zm9.8004 5.7 5.8602 5.87c-1.39 0.5-2.7901 1-4.2102 1.44l-4.4802-4.47a7.5203 7.52 0 0 1-1.9401 0.81 7.8303 7.83 0 0 1-6.0002-0.79 7.8703 7.87 0 0 1-2.9201-10.69v-0.07a7.8903 7.89 0 0 1 10.77-2.88l0.12 0.07a7.8603 7.86 0 0 1 2.7901 10.62v0.07zm-37.701-2.36-9.5004 9.51v4.9c-1.35-0.16-2.6801-0.33-4.0002-0.54v-6l0.58002-0.58 10.1-10.09a7.8703 7.87 0 1 1 2.8401 2.86zm7.3203-5.91a3.4601 3.46 0 1 0-1.6101 2.1 3.3801 3.38 0 0 0 1.6101-2.1zm-29.741 7.82 3.0901 3.1 0.59002 0.59v7.36c-1.3401-0.26-2.6801-0.55-4.0002-0.87v-4.84l-2.5101-2.51a7.5203 7.52 0 0 1-1.9401 0.81 7.8803 7.88 0 1 1 1.9101-14.43 7.8703 7.87 0 0 1 2.8901 10.75z\" style=\"fill:#1a1a1a;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m94.19 136.84h42.632a3.7801 3.78 0 0 1 3.7802 3.78v3.22a15.231 15.23 0 0 1-15.211 15.16h-19.781a15.251 15.25 0 0 1-15.221-15.16v-3.22a3.8002 3.8 0 0 1 3.7802-3.78z\" style=\"fill:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:3px;stroke:#1a1a1a;\"/><path d=\"m130.96 136.84v21.16m-30.911-21.16v21.16m10.34-21.16v22.16m10.31-22.2v22.2\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:3px;stroke:#1a1a1a;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m83.739 83.92h63.533a19.101 19.1 0 0 1 19.051 19 19.111 19.11 0 0 1-19.051 19h-63.533a19.091 19.09 0 0 1-19.001-19 19.091 19.09 0 0 1 19.001-19z\" style=\"fill:#1a1a1a;\"/><path d=\"m140.23 93.54a9.3804 9.38 0 1 0 9.3804 9.38 9.3804 9.38 0 0 0-9.3804-9.38zm-49.402 0a9.3804 9.38 0 1 0 9.3804 9.38 9.3904 9.39 0 0 0-9.3804-9.38z\" style=\"fill:#e6e7e8;\"/><rect x=\"79.795\" y=\"98.627\" width=\"71.471\" height=\"8.5859\" ry=\"4.2929\" style=\"fill:#b3b3b3;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m32.902 67.662c-0.36295 1.7227-6.2342 30.695 5.6133 52.596 4.5843 8.4743 9.0081 13.239 12.75 15.893a67.7 67.7 0 0 1-3.4688-21.35 67.7 67.7 0 0 1 2.332-17.658c-4.4914-2.4646-10.868-6.9012-13.834-13.52-4.1626-9.285-3.6155-14.673-3.3926-15.961zm165.19 0c0.22292 1.2882 0.77005 6.6759-3.3926 15.961-2.9664 6.6183-9.3426 11.055-13.834 13.52a67.7 67.7 0 0 1 2.332 17.658 67.7 67.7 0 0 1-3.4688 21.35c3.7419-2.6532 8.1657-7.4183 12.75-15.893 11.847-21.9 5.9762-50.873 5.6133-52.596z\" style=\"fill:#fff;\"/><path d=\"m115.73 13.191c-7.3787-0.13351-13.509 5.7888-13.631 13.168-0.10128 5.8827 3.4508 10.518 8.0566 12.52 1.061 0.46115 2.1869 0.78009 3.3418 0.95703v8.4291c0.66778-0.02035 1.3358-0.03077 2.0039-0.03125 0.66547-9e-5 1.3309 0.0097 1.9961 0.0293v-8.4115c2.6002-0.38406 5.1586-1.5484 7.3086-3.625 4.2322-4.0878 4.9991-9.8755 3.1582-14.549-1.8407-4.6726-6.3502-8.3834-12.232-8.4863z\" style=\"fill:#fff;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// 创建第二组形状集合 - Girl风格
|
||||
GirlStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5c0 10.76 11.75 19.48 26.25 19.48s26.25-8.72 26.25-19.48z\" style=\"fill:#1a1a1a;\"/><path d=\"m92.502 194.27v0.70391c0 4.3033 2.4373 8.2583 6.3807 11.183 4.2199 3.1204 10.106 5.0508 16.661 5.0508 6.548 0 12.434-1.9303 16.654-5.0508 3.9434-2.9245 6.388-6.8795 6.388-11.183v-0.67489c1.0768 0.21771 2.1463 0.44994 3.2158 0.69666h-7e-3c1.0695 0.24672 2.1318 0.50798 3.1867 0.791-0.27648 6.103-3.6524 11.553-8.9708 15.493-5.2821 3.9114-12.521 6.328-20.466 6.328-7.9449 0-15.184-2.4165-20.474-6.328-5.333-3.9477-8.7089-9.4194-8.9708-15.544 1.055-0.27577 2.1099-0.53702 3.1722-0.78376 1.0695-0.23947 2.1463-0.46443 3.2304-0.68213z\" style=\"fill:#b3b3b3;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m100.35 143.85a7.67 7.67 0 0 0 7.58 7.7v0a7.66 7.66 0 0 0 7.57-7.7 7.66 7.66 0 0 0 7.57 7.7v0a7.67 7.67 0 0 0 7.58-7.7\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.3998px;stroke:#333;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m78.73 111a10.9 10.9 0 0 1 15.19 0m43.16 0a10.9 10.9 0 0 1 15.19 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.1999px;stroke:#333;\"/><path d=\"m79.804 123.74h7.07m57.273 0h7.05\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#b8b8b8;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m57.534 142.03c-6.9383-31.75-0.57294-52.577 14.174-62.344 22.562-12.283 62.082-12.222 83.484-1.8846 21.348 11.177 22.124 37.396 18.498 63.733 8.1279-14.155 13.164-31.598 14.085-48.902 1.0828-11.795-1.1756-18.866-7.4833-27.972-26.465-37.685-103.45-31.56-129.66-2.8372-7.8504 9.4615-9.6006 17.478-9.275 26.667 1.0024 18.667 6.9688 38.508 16.18 53.54z\" style=\"fill:#b3b3b3;\"/><path d=\"m111.26 3.0423c-6.013 0.1128-12.629 2.6924-15.291 7.9082-1.1676 3.2383-1.6758 6.2069-1.6758 8.8926 0.89228-0.2661 1.8005-0.5164 2.7266-0.7441 3.7502-1.0672 7.4851-1.7135 11.129-1.9981 1.1007-0.086 2.1953-0.1391 3.2773-0.1601h2e-3c5.6969-0.1133 11.09 0.6603 15.904 2.0527 4.8141 1.3924 8.6914 3.5977 10.533 6.6738 1.8407 3.0761 1.8407 7.0137 0 10.09-1.8407 3.0761-5.7179 5.2814-10.533 6.6738-4.8141 1.3924-10.207 2.166-15.904 2.0527-5.6969-0.1133-11.09-1.1133-15.904-2.5057-4.8141-1.3924-8.6914-3.5977-10.533-6.6738-1.8407-3.0761-1.8407-7.0137 0-10.09 1.8407-3.0761 5.7179-5.2814 10.533-6.6738 4.8141-1.3924 10.207-2.166 15.904-2.0527z\" style=\"fill:#fff;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Blonde 风格
|
||||
BlondeStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5c0 10.76 11.75 19.48 26.25 19.48s26.25-8.72 26.25-19.48z\" style=\"fill:#5a5a5a;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m115.5 161.71c-8.24 0-14.46-4.15-19.19-11.25 3.37-2.44 6.51-4.57 10-6.79a5.25 5.25 0 0 1 5.48-0.17 28.19 28.19 0 0 1 3.68 2.75 28.19 28.19 0 0 1 3.68-2.75 5.25 5.25 0 0 1 5.48 0.17c3.52 2.22 6.66 4.35 10 6.79-4.74 7.1-11 11.25-19.19 11.25z\" style=\"fill:#5a5a5a;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m172.7 90.75h-6.54c-0.14-0.1-0.26-0.22-0.4-0.3-4.48-2.76-22.75-2.11-33.71 1.2-1 0.3-1.91 0.61-2.75 0.94-1.8937 0.79244-3.8739 1.3597-5.9 1.69-5.5051 0.79002-10.403 0.79002-15.908 0-2.0261-0.33034-4.0063-0.89756-5.9-1.69-0.84-0.33-1.76-0.64-2.75-0.94-11-3.31-29.23-4-33.71-1.2-0.13832 0.08869-0.2688 0.18906-0.39 0.3h-6.55c-1.1046 0-2 0.89543-2 2v4.66c-0.0013 0.98185 0.49088 1.8986 1.31 2.44l1.9 1.27c0.59238 0.38889 0.93475 1.0622 0.9 1.77-0.14175 5.4854 0.88072 10.939 3 16 3.58 8.38 16 10.9 24.93 10.9 2.6976 0.0771 5.3921-0.2361 8-0.93 4.35-1.43 8.24-7.36 10.45-12.42 1.7607-3.8506 2.7493-8.009 2.91-12.24 7.3e-4 -0.7138 0.38183-1.3731 1-1.73 3.2281-1.951 6.5798-1.951 9.8079 0 0.61817 0.3569 0.99927 1.0162 1 1.73 0.16067 4.231 1.1493 8.3894 2.91 12.24 2.21 5.06 6.1 11 10.45 12.42 2.6079 0.6939 5.3024 1.0071 8 0.93 8.92 0 21.35-2.52 24.93-10.9 2.1193-5.0614 3.1418-10.515 3-16-0.0348-0.70778 0.30762-1.3811 0.9-1.77l1.9-1.27c0.81913-0.54136 1.3113-1.4582 1.31-2.44v-4.6c0.0336-1.1048-0.83521-2.0274-1.94-2.06z\" style=\"fill:#1a1a1a;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.5;stroke:#b3b3b3;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m124.22 13.61c-19.783 0-36.945 8.0887-39.695 24.106-15.332 0.23539-31.831 2.7712-41.663 15.782-6.0238 7.9604-7.0402 19.901-6.8476 31.724 0.46007 28.503 10.742 64.228-4.3012 89.714 16.584 5.7777 43.086 10.742 73.59 11.662v-8.6558c-1.851-0.35308-3.6592-0.78105-5.4353-1.2732-30.953-8.4632-50.672-36.635-47.259-68.669 1.5514-10.603 4.6221-19.665 10.025-27.69 5.3818-7.9925 13.267-15.717 23.892-21.41 0.40658 0.72757 1.9901 3.5843 2.4074 4.3012 7.5003 12.775 17.986 23.849 33.157 26.866 12.433 2.4609 23.849 3.4666 36.346 1.1555 4.2584-0.78106 10.667-2.3967 14.851-2.4181 14.861 33.404-1.0806 75.035-40.668 87.457-2.2255 0.70616-4.5258 1.316-6.8904 1.8189 0 2.707-0.0428 5.6493-0.0642 8.5274 23.603-0.72757 48.682-4.0444 72.874-11.234-18.521-32.152 0.81315-89.083-10.036-121.46-9.0731-26.973-38.85-40.315-64.282-40.305z\" style=\"fill:#c5c5c5;\"/><path d=\"m33.147 172.32c-2.6535 5.1143-6.088 9.9504-10.1 12.411 7.8427 10.453 17.387 19.516 28.257 26.781 16.038-10.731 35.629-17.055 54-18.606v-9.0089c-30.065-0.94155-56.108-5.8847-72.157-11.577zm164.06 0.55637c-23.731 7.0723-48.361 10.325-71.525 11.042-0.0321 3.1242-0.0535 6.2377-0.0107 9.0517 19.227 1.7226 37.908 7.8534 53.989 18.542 0.0107 0 0.0107 0 0.0214 0.0107 10.731-7.1686 20.179-16.081 27.958-26.374-4.2798-2.3967-7.832-6.9653-10.432-12.272z\" style=\"fill:#c5c5c5;\"/><path d=\"m50.02 46.5c-2.9297 1.9143-6.1313 3.8826-10.154 7.9805-14.091 14.359-16.145 27.701-6.1406 44.018 4.2049 6.8583 6.1414 13.706-0.24609 20.5-7.7143 8.1957-21.559 4.2912-21.537 16.061 0.0214 8.613 15.063 7.9178 22.531 13.984 3.7662 3.0707 5.0836 8.3992 2.0664 12.508-4.2156 5.7456-16.006 7.3715-22.629 8.9336 5.8811 10.843 13.45 20.638 22.355 29.033l0.0039 0.0234 0.0059-0.0137c2e-3 2e-3 0.0038 4e-3 0.0059 6e-3 0.0034-0.0112 0.0063-0.0219 0.0098-0.0332 14.775-12.218 20.268-20.965 49.461-28.434-17.404-10.258-30.68-27.122-24.143-35.34 4.4123-5.5444 5.6612-7.8633 6.4062-12.078 2.3582-13.339-10.208-22.335-9.2363-32.715 1.9432-8.2346 11.379-11.173 16.947-15.115 5.4577-3.9082 9.8014-8.7695 10.799-16.918-13.558-4.8896-17.609-5.8617-36.506-12.4zm140.87 19.357c-3.4404-0.91243-23.311 122.43 4.4121 133.14 8.9661-8.5809 16.552-18.584 22.404-29.658 0-0.31029-25.133-3.9922-25.979-14.018-0.10699-1.1769 0.11822-1.4855 0.86718-2.502 6.6764-9.2122 30.716-11.416 29.646-23.496-0.27818-3.1563-4.1617-5.2334-6.7402-6.4531-12.155-5.767-32.942-9.6494-15.031-24.543 9.2122-7.3505 10.43-8.4323 0.59766-14.691-9.4583-6.0238-9.394-11.993-9.7578-16.326-0.0767-0.93035-0.22089-1.4003-0.41992-1.4531z\" style=\"fill:#c5c5c5;\"/><path d=\"m133.83 39.909c-11.33 1.393-9.5492 16.204-2e-3 16.643-4.5102 10.717 9.0165 16.181 14.441 8.3125 6.562 8.6765 18.596 0.94751 14.457-8.3125 11.718-1.5381 9.2769-16.099 0-16.643 4.503-10.867-9.4883-16.101-14.457-8.3301-6.8832-9.0411-18.509-0.47321-14.439 8.3301z\" style=\"fill:#333;\"/><path d=\"m153.86 48.222c0-3.0528-2.5184-5.5648-5.5791-5.5648-3.0783 0-5.5793 2.512-5.5793 5.5648 0 3.0703 2.501 5.5648 5.5793 5.5648 3.0606 0 5.5791-2.4946 5.5791-5.5648z\" style=\"fill:#f9f9f9;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
// Guy 形状集合
|
||||
GuyStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195c13.563 3.1499 26.439 8.7409 38 16.5-38.873 26.001-89.587 26.001-128.46 0 11.561-7.7591 24.437-13.35 38-16.5 8.4869 8.8011 26.21 25.619 26.21 25.619s17.603-16.972 26.25-25.619z\" style=\"fill:#d6d6d6;\"/><path d=\"m109 230.81 1.6836-14.33h9.6328l1.6836 14.33c-2.16 0.12-4.33 0.19-6.51 0.19s-4.35-0.07-6.51-0.19z\" style=\"fill:#5e5e5e;\"/><path d=\"m124.17 210.6h-17.349v5.53a3.8828 3.29 0 0 0 3.8828 3.29h9.583a3.8828 3.29 0 0 0 3.8828-3.29z\" style=\"fill:#535353;\"/><path d=\"m140.57 190.36-25.066 20.245c5.9686 3.2455 11.597 7.0814 16.8 11.45 1.5989 1.3338 3.9762 1.1189 5.31-0.48 0.21005-0.25749 0.38802-0.53956 0.52999-0.84l10.826-23.805-4-6c-0.90256-1.351-2.7298-1.7137-4.08-0.81-0.11612 0.0786-0.22641 0.16549-0.33 0.26z\" style=\"fill:#c6c6c6;\"/><path d=\"m90.434 190.36 25.066 20.245c-5.9686 3.2455-11.597 7.0814-16.8 11.45-1.5989 1.3338-3.9762 1.1189-5.31-0.48-0.21005-0.25749-0.38802-0.53956-0.52999-0.84l-10.826-23.805 4-6c0.90256-1.351 2.7298-1.7137 4.08-0.81 0.11612 0.0786 0.22641 0.16549 0.33 0.26z\" style=\"fill:#c6c6c6;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m136.21 147.09a21.77 21.77 0 0 1-40.13 0z\" style=\"fill:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.4999px;stroke:#000;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m145.39 104.7-11.52 11.2h17.26m-65.52-11.2 11.52 11.2h-17.26\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.4998px;stroke:#000;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m43.891 77.836c-5.1124 28.237 2.1347 61.004 24.792 81.332-6.2362-12.503-9.5362-33.948-9.4887-45.458-0.50203-37.473 41.439-46.335 56.149-17.614 18.8-31.2 52.825-16.872 54.062 13.714 0.56018 13.844-0.43568 25.598-7.0962 48.966 18.372-12.47 28.012-53.959 23.545-80.941-47.486-2.2552-94.831-2.5724-141.96 0z\" style=\"fill:#1a1a1a;\"/><path d=\"m111.26 12.782c-18.508 0.0791-32.594 3.6163-32.594 3.6163 24.513 5.6002 32.807 10.504 31.743 19.835-0.87227 9.702-11.092 10.875-20.811 11.554-5.2548 0.36414-10.949 0.71523-16.391 1.7525-11.862 2.2818-19.946 4.3736-24.447 11.956-1.7012 2.8662-3.7945 10.428-4.8689 16.34h141.96c-5.7242-38.563-32.557-65.073-74.595-65.054z\" style=\"fill:#1a1a1a;\"/><path d=\"m73.292 44.77c-11.788 2.2816-18.923 5.5444-23.394 13.126-2.8484 6.7586-4.8454 13.238-6.0072 19.939h141.96c-1.9772-14.576-6.8677-28.248-19.277-32.098-28.834-6.3308-63.774-6.3553-93.285-0.96761z\" style=\"fill:#1a1a1a;\"/><path d=\"m165.95 35.642c-11.178 21.829-91.89 19.36-103.98 2.3011-9.703 12.267-15.605 25.883-18.079 39.892h141.96c-3.0096-17.158-9.7424-32.688-19.902-42.193z\" style=\"fill:#1a1a1a;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Country 风格
|
||||
CountryStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5l15.71 15.75h21z\" style=\"fill:#949494;\"/><path d=\"m115.45 211.34-10.55 10.54a2.51 2.51 0 0 1-3.5599 0 2 2 0 0 1-0.26999-0.30994l-18.48-25.4 5.8901-5.8899a2.52 2.52 0 0 1 3.5199-0.0791l23.49 21.14z\" style=\"fill:#c0c0c0;\"/><path d=\"m115.45 211.34 10.55 10.54a2.51 2.51 0 0 0 3.5599 0 2 2 0 0 0 0.26999-0.30994l18.48-25.4-5.8901-5.8899a2.52 2.52 0 0 0-3.4699-0.089l-23.49 21.14z\" style=\"fill:#c0c0c0;\"/><path d=\"m158.41 199.58-10.11-3.2401v29.93q5.1601-1.5299 10.11-3.51zm-75.82 26.66v-29.9l-10.1 3.2401v23.14c3.2901 1.3199 6.67 2.4999 10.1 3.5199z\" style=\"fill:#7c7c7c;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m118.05 148.38c-1.5064 0.59192-2.595 2.0264-2.6191 3.9863-0.0574 1.3977 0.53421 3.5611 3.6758 5.7949 8.0544 4.9446 21.507 3.6862 21.255-7.1658-4.664 4.8219-10.021 5.6377-14.773 0.73907-1.2328-1.1599-2.3694-2.4032-3.9294-3.1408-1.0946-0.50424-2.2257-0.61071-3.6096-0.21337z\" style=\"fill:#333;\"/><path d=\"m133.61 154.93c3.0731-0.48816 5.5702-2.8457 5.4438-4.5059-0.47801-4.8311-5.7317-3.0917-4.3369-0.31405-2.8103-1.4445-1.8343-3.8862 0.50427-4.7324 2.0509-0.79942 5.0937 0.34314 6.2002 2.6376 2.2229 7.3422-3.4376 11.68-10.384 12.561z\" style=\"fill:#333;\"/><path d=\"m112.81 148.38c1.5064 0.59192 2.595 2.0264 2.6191 3.9863 0.0574 1.3977-0.53421 3.5611-3.6758 5.7949-8.0544 4.9446-21.507 3.6862-21.255-7.1658 4.664 4.8219 10.021 5.6377 14.773 0.73907 1.2328-1.1599 2.3694-2.4032 3.9294-3.1408 1.0946-0.50424 2.2257-0.61071 3.6096-0.21337z\" style=\"fill:#333;\"/><path d=\"m97.252 154.93c-3.0731-0.48816-5.5702-2.8457-5.4438-4.5059 0.47801-4.8311 5.7317-3.0917 4.3369-0.31405 2.8103-1.4445 1.8343-3.8862-0.50427-4.7324-2.0509-0.79942-5.0937 0.34314-6.2002 2.6376-2.2229 7.3422 3.4376 11.68 10.384 12.561z\" style=\"fill:#333;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m131.64 114.09 7.5801-7.5801 7.5801 7.5801m-62.6 0 7.5801-7.5801 7.5799 7.5801\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.4998px;stroke:#000;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m137.38 11.148c-12.23 1.9593-18.511 14.606-43.436 9.4915-11.285-3.2054-16.406-3.573-20.389 0.58594-4.1548 4.3384-7.033 12.435-9.8184 21.706-2.1354 7.4136-3.7187 14.381-4.7461 21.646h112.7c-3.4878-24.293-10.822-43.281-25.182-51.061-3.5314-1.623-6.5274-2.2959-9.1289-2.3613z\" style=\"fill:#b3b3b3;\"/><path d=\"m114.37 43.383c-19.445 0.088-38.524 2.0724-52.379 5.6992-1.2766 4.5795-2.4317 10.169-3.2285 16.807h113.11c-0.83731-6.0107-1.9164-11.674-3.3184-16.924-15.229-3.8842-34.873-5.6693-54.18-5.582z\" style=\"fill:#e6e6e6;\"/><path d=\"m115.5 55.773c-58.39 0-105.73 15.476-105.73 34.57h0.0312c0 11.295 16.496 21.319 42.126 27.627-0.10331-7.7704 2.788-21.904 5.2734-31.031 6.0935-1.7168 6.9294-1.8971 13.167-2.9919 14.874-2.8256 29.99-4.2037 45.133-4.1153 15.143-0.0884 30.259 1.2897 45.133 4.1153 6.2372 1.0947 7.2065 1.2751 13.3 2.9919 2.4854 9.1267 5.3768 23.26 5.2734 31.031 25.63-6.3082 41.993-16.332 41.993-27.627h0.0312c0-19.093-47.34-34.57-105.73-34.57z\" style=\"fill:#818181;\"/><path d=\"m72.088 83.533c-6.9765 1.1147-13.357 2.856-18.439 4.3477-1.1861 7.415-2.0038 18.858-1.8926 26.293 4.3278-0.62795 10.155-1.3644 13.295-1.6465-0.40554 0.30198 2.7344-17.827 7.0371-28.994zm86.824 0c4.3028 11.167 7.4426 29.296 7.0371 28.994 3.1396 0.28213 8.9671 1.0185 13.295 1.6465 0.11119-7.4351-0.70652-18.878-1.8926-26.293-5.0822-1.4916-11.463-3.2329-18.439-4.3477z\" style=\"fill:#434343;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Geeknot 风格
|
||||
GeeknotStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 194.98a114.79 114.78 0 0 1 38 16.498 115.53 115.52 0 0 1-128.46 0 114.79 114.78 0 0 1 38-16.498l15.71 15.748h21z\" style=\"fill:#d2d2d2;\"/><path d=\"m70 200.88v20.77c-2.22-0.95325-4.3999-1.9698-6.5399-3.0496h-0.10088v-14.621c2.17-1.1 4.39-2.1399 6.64-3.0996z\" style=\"fill:#505050;\"/><path d=\"m161 200.88v20.77c1.9-0.80986 3.7702-1.6798 5.6201-2.5898l0.0989-0.0494 0.82005-0.40997h0.10088v-14.621c-2.17-1.1-4.39-2.1399-6.6402-3.0996z\" style=\"fill:#505050;\"/><polygon transform=\"matrix(1 0 0 .99987 4e-5 -3e-5)\" points=\"97.32 201.93 115.5 223.72 133.68 201.93\" style=\"fill:#171717;\"/><path d=\"m111.2 230.88 1.31-16.908c0.32992 1.2798 5.6399 1.2798 5.9999 0l1.3201 16.938c-1.4301 0.0494-2.8601 0.089-4.3 0.089s-2.87 0-4.3-0.089z\" style=\"fill:#171717;\"/><path d=\"m115.49 201.79v0.0692l-7.55 12.678-7.0001 11.809-19.19-26.487c0.60999-0.42995 1.22-0.89985 1.8001-1.3899a52 51.993 0 0 0 10.07-10.619l21.79 13.878z\" style=\"fill:#ebebeb;\"/><path d=\"m149.24 199.86-19.08 26.517-7.0001-11.809-7.57-12.678-0.0593-0.10086 21.94-13.998a52.21 52.203 0 0 0 10.08 10.699c0.58013 0.47009 1.1502 0.92002 1.7301 1.3399z\" style=\"fill:#ebebeb;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m122.83 151.88a10.49 10.489 0 0 1-14.66 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.1996px;stroke:#333;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m70.959 94.985h35.031c2.4086 1e-5 4.3612 1.9523 4.3612 4.3606l-2.5864 17.511c-0.3515 2.3799-1.7218 4.3606-3.8457 4.3606h-30.9c-2.1239-1e-5 -3.8457-1.9523-3.8457-4.3606l-2.5864-17.511c1e-5 -2.4082 1.9526-4.3606 4.3612-4.3606z\" style=\"fill:#1a1a1a;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.0045px;stroke:#333;\"/><path d=\"m160.05 94.985h-35.031c-2.4086 1e-5 -4.3612 1.9523-4.3612 4.3606l2.5864 17.511c0.35149 2.3799 1.7218 4.3606 3.8457 4.3606h30.9c2.1239-1e-5 3.8457-1.9523 3.8457-4.3606l2.5864-17.511c-1e-5 -2.4082-1.9526-4.3606-4.3612-4.3606z\" style=\"fill:#1a1a1a;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.0045px;stroke:#333;\"/><path d=\"m90.607 102.35a4.6337 4.6332 0 1 0 4.6892 4.6337 4.6337 4.6332 0 0 0-4.6892-4.6337zm49.72 0a4.6337 4.6332 0 1 0 4.6444 4.6337 4.6337 4.6332 0 0 0-4.6444-4.6337z\" style=\"fill:#1a1a1a;\"/><path d=\"m70.66 94.985h-11.775\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.0045px;stroke:#333;\"/><path d=\"m172.13 94.985h-19.484\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.0045px;stroke:#333;\"/><path d=\"m109.32 106.2c4.2045-2.427 9.3036-1.913 12.353-0.0258\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.0045px;stroke:#333;\"/><path d=\"m148.33 109.79-5.7626-8.2324\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4;stroke:#fff;\"/><path d=\"m156.27 105-2.403-3.4328\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4;stroke:#fff;\"/><path d=\"m82.748 114.34-8.9489-12.784\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4;stroke:#fff;\"/><path d=\"m91.408 109.79-5.7626-8.2324\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4;stroke:#fff;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m41.835 75.131c-2.8674 12.582 1.2304 27.241 6.0238 39.031 0.25861 0.63658 0.51208 1.3075 0.79989 1.9683 0.71726 1.658 2.1184 3.9751 3.0038 3.9266 0.56895-0.0312 0.71637-1.5512 1.0228-3.1562 2.1988-19.097 8.8981-27.915 15.636-38.107 2.8783-4.0645 3.8616-7.2293 1.0644-9.9325-6.3236-3.5596-14.924-2.8574-21.367-0.67406-3.2312 1.4765-5.2427 3.4773-6.1842 6.9439zm125.65-8.5679c7.65-0.70616 19.714-0.1307 21.694 8.5679 1.455 6.4083 0.26915 17.747-1.0542 24.579-1.1961 5.3203-3.8066 14.231-7.8782 19.75-0.5565 0.44544-0.96888 0.13656-1.4159-1.1606-0.90692-3.0353-1.4298-7.8372-2.2556-10.727-3.4822-12.79-8.2195-21.875-14.429-29.94-5.5782-6.8415-4.2152-9.7207 5.3393-11.069z\" style=\"fill:#4d4d4d;\"/><path d=\"m112.27 73.826c-18.585-7.5217-34.987-14.797-48.939 5.018-4.9752 7.083-3.7876 8.8056-4.9217 0.0749-1.637-12.476-4.7505-34.174 1.9259-45.194 7.6822-12.7 19.323-13.128 31.039-5.3818 10.796 7.7784 24.277 14.647 38.015 12.219 12.732-2.2576 15.835-7.7464 15.707-19.912-0.0215-2.6-0.0963-5.2106-0.2033-7.7999 13.631 3.9267 24.609 14.776 26.513 29.049 0.88804 6.6336 0.26749 12.722-1.9259 19.013-5.9702 17.108-30.119 20.896-45.74 16.841-3.9588-1.0378-7.6822-2.4181-11.47-3.9267z\" style=\"fill:#4d4d4d;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Asian 风格
|
||||
AsianStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m115.5 231a115 115 0 0 0 64.23-19.5 114.79 114.79 0 0 0-38-16.5l-2.41-9a125.19 125.19 0 0 0-13.32-2.28v8.75q3.52 0.32 7 0.84l-17.5 17.48-17.5-17.48q3.45-0.52 7-0.84v-8.75a125.55 125.55 0 0 0-13.34 2.28l-2.41 9a114.79 114.79 0 0 0-38 16.5 114.94 114.94 0 0 0 64.25 19.5z\" style=\"fill:#646464;\"/><path d=\"m132.98 193.33-36.185 36.155-2.4-0.42 36.108-36.081z\" style=\"fill:#e3e3e3;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m127.84 146.73c-2.24 8.93-6.92 15.08-12.34 15.08s-10.1-6.15-12.34-15.08z\" style=\"fill:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.9999px;stroke:#1a1a1a;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m129.31 114.14 20-5.37m-47.66 5.37-20-5.37\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4.9998px;stroke:#1a1a1a;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m169.65 90.998c3.137 11.94 4.9371 36.484-3.4118 58.213l5.129 3.1164c10.044-15.199 14.959-39.163 13.943-61.33z\" style=\"fill:#1a1a1a;\"/><path d=\"m45.081 90.989c-0.88085 4.9304-0.87534 14.953-0.15027 21.75 2.1318 19.98 16.671 42.505 16.671 42.505l5.7352-4.4331s-13.244-31.348-6.0571-52.751c0.52108-1.5517 0.95592-2.916 1.3462-4.1835z\" style=\"fill:#1a1a1a;\"/><path d=\"m117 3.4883c-8.2136-0.19887-19.13 7.933-18.494 9.3516 1.6214 3.6186 11.176 22.55 11.889 23.963h10.148c2.6022-6.3102 11.32-26.531 11.32-26.531s-4.1382-4.138-12.416-6.4375c-0.77605-0.21556-1.5976-0.32513-2.4473-0.3457z\" style=\"fill:#1a1a1a;\"/><path d=\"m115.95 4.5428c-3.1563 0-6.3123 0.57462-9.2165 1.715-5.8084 2.2817-10.532 6.808-12.779 12.245v-5e-3c-1.8166 4.397-2.0233 9.3441-0.58058 13.857 0.69352 2.1687 1.7693 4.2296 3.1533 6.0968h38.893c0.71032-0.95769 1.3441-1.9641 1.8787-3.0144 2.6811-5.2673 2.9296-11.542 0.67253-16.975-2.257-5.4337-6.9893-9.9522-12.802-12.224-2.9064-1.1335-6.0633-1.6987-9.2196-1.6956z\" style=\"fill:#1a1a1a;\"/><path d=\"m92.512 28.125c0.13387 1.4318 0.41877 2.8511 0.85962 4.2306 1.4429 4.5127 4.5278 8.5654 8.6411 11.353 4.1135 2.7873 9.2311 4.2913 14.336 4.2165 5.1052-0.0764 10.168-1.7333 14.181-4.6419 2.8754-2.0834 5.2132-4.7932 6.7665-7.8447 1.2005-2.3586 1.9085-4.9188 2.127-7.5156-15.037-2.6407-31.421-3.4671-46.912 0.20253z\" style=\"fill:#b3b3b3;\"/><path d=\"m34.426 90.63c14.714 4.0779 22.683 6.4085 45.254 7.4257 2.5318-18.185 4.6689-28.672 10.023-38.352 3.2025 13.403 3.8346 25.22 2.9106 42.253l11.172-0.23161c1.4706-11.886 3.8989-29.213 2.1636-42.021 10.416 12.631 11.373 23.624 13.077 39.726 30.174-0.76004 59.808-4.5121 77.845-10.128-10.76-38.608-41.475-55.66-80.38-56.104-38.182-0.45134-74.543 22.405-82.065 57.432z\" style=\"fill:#1a1a1a;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Punk 风格
|
||||
PunkStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m88.18 194.11c-4.2079 1.021-8.3545 2.2792-12.42 3.7695v26.072a115.5 115.5 0 0 0 79.48 0v-26.072c-4.0858-1.4904-8.2529-2.7486-12.48-3.7695v8.7051c0 9.3888-7.6112 17-17 17h-20.58c-9.3888 0-17-7.6112-17-17v-8.7051z\" style=\"fill:#efefef;\"/>",
|
||||
TypeMouth: "<polygon id='mouth' points=\"121.61 160.74 109.39 160.74 115.5 171.31\" style=\"fill:#797979;\"/><path d=\"m132.64 144.06a34.42 34.42 0 0 1-34.24 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#000;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m170.25 100c1.69 9.62-4.79 29.23-22.4 29.23-6.81 0-15-3.66-20.23-10-4.34-5.33-7.56-12.87-6.2-19.45 1.63-7.89 7.07-11.45 14.67-12.92a68.16 68.16 0 0 1 12.52-1c10.77 0 19.78 3.61 21.64 14.22z\" style=\"fill:#565656;stroke-width:3.99px;stroke:#000;\"/><path d=\"m60.75 100c-1.69 9.62 4.79 29.23 22.4 29.23 6.81 0 15-3.66 20.23-10 4.34-5.33 7.56-12.87 6.2-19.45-1.63-7.89-7.07-11.45-14.67-12.92a68.16 68.16 0 0 0-12.52-1c-10.77 0-19.78 3.61-21.64 14.22z\" style=\"fill:#565656;stroke-width:3.99px;stroke:#000;\"/><line x1=\"100.2\" x2=\"130.8\" y1=\"87.92\" y2=\"87.92\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:3.99px;stroke:#000;\"/><path d=\"m109.87 101.73c0-2.59 2.52-4.69 5.63-4.69s5.63 2.1 5.63 4.69\" style=\"fill:transparent;stroke-width:3.99px;stroke:#000;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m30.622 70.381c2.0971-3.9374 4.6649-7.9604 7.6822-12.037 3.0172-4.0765 6.0987-7.6929 9.2229-10.817l22.897 22.897c-4.4402 4.4403-8.2278 9.5439-11.213 15.14z\" style=\"fill:#999;\"/><path d=\"m160.58 70.423 22.907-22.897c3.1242 3.1242 6.2056 6.7406 9.2229 10.817 3.0065 4.0765 5.5744 8.0994 7.6715 12.037l-28.578 15.182c-2.9851-5.5958-6.7727-10.689-11.224-15.14z\" style=\"fill:#999;\"/><path d=\"m92.411 15.247c3.8197-0.87736 7.6715-1.5407 11.534-1.9794 4.0765-0.46007 7.9282-0.69546 11.555-0.69546 1.53 0 3.1563 0.0428 4.8682 0.1391l1.851 22.255 5.767-21.57c3.1028 0.37449 6.0666 0.86666 8.8912 1.4658l-10.55 49.763c-1.9259-0.41729-3.702-0.70617-5.3176-0.87736-1.423-0.14979-3.2633-0.22468-5.5102-0.22468-2.2362 0-4.237 0.10699-5.981 0.29958-1.9473 0.22469-3.8732 0.55636-5.767 0.99504z\" style=\"fill:#999;\"/><path d=\"m92.411 15.247c1.9152-0.43869 4.023-0.84526 6.3233-1.2304 2.065-0.34238 4.1514-0.62057 6.2698-0.84525l5.1785 50.565c-1.0913 0.10699-2.1827 0.25679-3.2954 0.43868-0.86665 0.14979-1.9152 0.36378-3.1349 0.64196z\" style=\"fill:#4d4d4d;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Afrohair 风格
|
||||
AfrohairStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.89 195a114.79 114.79 0 0 1 38 16.5 115.55 115.55 0 0 1-128.47 0 114.79 114.79 0 0 1 38-16.5l15.75 15.75h21z\" style=\"fill:#353535;\"/><path d=\"m146.4 196.14-17.4 17.44-1.17 1.17h-24.34l-1.18-1.17-17.43-17.44c1.49-0.41 3-0.79 4.51-1.14l4.67-1 12.74 12.74h17.69l12.73-12.74 4.67 1c1.52 0.35 3 0.73 4.51 1.14z\" style=\"fill:#919191;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m115.68 160.64c7.08 0 13.11-4.93 15.46-11.84a2.14 2.14 0 0 0-1.51-2.6101 2.3 2.3 0 0 0-0.73995-0.0593h-26.42a2.12 2.12 0 0 0-2.31 1.9099 1.85 1.85 0 0 0 0.0593 0.73995c2.3401 6.9301 8.3802 11.86 15.46 11.86z\" style=\"fill:#2f2f2f;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m145.38 95.628c-5.1601 2.2597-11.03 2.2597-16.19 0m-47.29 1.75c5.1755-2.2694 11.065-2.2694 16.24 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#5e5e5e;\"/><path d=\"m90.016 106.28c-4.4506-0.0105-6.6902 5.3657-3.5508 8.5195 3.1394 3.1539 8.5252 0.93887 8.5352-3.5117 0.0063-2.7522-2.2204-4.9898-4.9727-4.9961l-0.011719-0.01172zm47.281 0c-4.4506-0.0105-6.6902 5.3657-3.5508 8.5195 3.1394 3.1539 8.5252 0.93887 8.5352-3.5117 6e-3 -2.7522-2.2204-4.9898-4.9727-4.9961l-0.01171-0.01172z\" style=\"fill:#1a1a1a;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m108.37 22.019c-6.2698-12.829-17.151-13.396-18.949 1.1769-11.448-9.4583-26.021-4.483-20.361 12.422-12.251-7.9282-24.919 1.7761-17.076 20.853-27.08 2.3646-22.715 24.726-10.111 31.435-9.9002 3.3566-10.701 9.4006-8.464 14.497 2.6574 4.7842 9.0126 6.4737 11.545 9.6519-6.624 0.59419-8.4112 5.6011-5.7404 9.5192 1.6896 2.4787 5.2756 4.2218 8.5971 5.5455 1.0485 0.40658 3.702 1.2732 3.9053 2.4181 0.18744 1.2156-6.7884 3.0055-5.7281 5.2612 0.60648 1.4227 1.7764 2.7151 2.6466 3.7156 1.2807 1.6595 10.755 8.0351 9.4583 4.2049-1.0271-3.7234-2.2148-7.4682-3.1456-11.192-1.1662-5.3069-1.7868-10.721-1.102-16.156 1.4223-5.455 5.069-4.4265 7.7837-8.3588 3.5264-5.7505 2.0296-11.614 2.124-13.575 0.107-1.7868 1.5407-1.1876 3.1884-1.4337 4.3868-0.64196 7.0081-2.1185 8.8377-6.2698 0.77035-1.9259 0.62057-9.7578 0.52426-11.78 0.36378-4.6328 4.1835 0 6.548 0.64196 3.2633 0.88805 6.8797 0.21399 9.0731-2.5037 1.7547-2.3753 2.0864-2.8888 4.6114-0.80245 2.6856 2.2148 4.0979 3.1349 7.6929 3.274 5.5637 0.20329 8.7735-6.2698 11.32-5.6386 3.5201 0.87735 3.6057 5.4567 10.261 4.8682 2.386-0.20329 3.8304-0.86665 5.4032-2.6428 0.88805-0.99505 1.958-2.5037 3.4345-2.6214 1.4658-0.1177 2.3218 2.3646 3.0065 3.4452 1.1926 2.6755 4.0295 3.6513 6.2377 3.3168 1.958-0.17119 3.854-1.4115 5.4268-2.4707 0.99679-0.66102 1.8284-0.81128 1.9256 0.2071 0.29592 2.2271 0.0862 7.7025 0.1596 8.4821 0.10556 8.4609 5.37 10.569 13.223 10.333-0.31871 3.7464 0.0583 11.28 5.4353 14.562 3.9481 2.7604 6.6657 1.2732 6.7299 7.8534 7e-3 6.1914-0.43693 13.061-1.2946 18.189-0.69547 4.0444-1.2412 6.4838-2.5251 10.378-0.64196 1.9152-0.81315 1.9687 1.4123 1.0699 7.1472-3.1456 10.539-11.48 8.3562-18.842-0.43869-2.0436 0.84525-1.7226 2.8781-2.6106 9.5248-4.2363 8.1264-11.335-0.75967-14.273 11.988-3.0926 13.886-8.9002 6.6871-15.375 7.3077-5.9168 3.6378-16.177-2.8032-16.991 12.422-7.0937 5.7349-22.062-5.1036-18.499 4.1728-12.037-5.5637-26.203-21.121-16.894 6.9653-11.373 2.065-22.661-12.101-10.785-3.4559-18.382-15.14-16.584-23.902-5.018 0.09435-20.075-16.001-17.42-18.146-2.5892z\" style=\"fill:#1a1a1a;\"/><path d=\"m5.4353 80.502c7.4468 9.1373 15.632 8.8912 15.632 8.8912s-6.0772 3.7983-6.8369 9.8755c-0.75966 6.088 4.5579 9.6295 8.0994 10.646 3.5522 1.0058 7.0937-2.7925 7.0937-2.7925s-5.8312 10.646-1.5193 15.964c4.3012 5.3176 11.908 3.0386 11.908 3.0386s-5.3283 10.132 1.0057 14.187c5.8312 3.7234 18.542 7.6715 20.511 8.2706-6.0666-9.7472-9.576-21.249-9.576-33.575v-0.0428c0-35.201 28.546-63.747 63.747-63.747 35.212 0 63.758 28.546 63.758 63.747 0 12.476-3.5843 24.116-9.7899 33.949h0.53496s13.931-1.0057 16.21-9.3727c2.279-8.3562 0.75967-9.8756 0.75967-9.8756s10.635 2.0329 13.417-7.5966l2.7926-9.6295s10.132 0 10.892-7.083c0.75963-7.0937-7.0295-12.411-7.0295-12.411s11.459 0.82385 14.498-10.453c1.0164-3.7555 0.83456-8.2171 0.1391-12.497-17.665-41.161-58.569-69.995-106.18-69.995-30.632 0-60.034 12.187-81.679 33.831v0.0107c-13.171 13.171-22.833 29.22-28.386 46.66z\" style=\"fill:#1a1a1a;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Normie Female 风格
|
||||
NormieFemaleStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5l13.85 13.85v-1.2h17.86v3.1h5z\" style=\"fill:#333;\"/><polygon points=\"115.36 207.65 123.37 224.2 148.3 196.86 143.08 189.95\" style=\"fill:#fff;\"/><polygon points=\"115.36 207.65 107.35 224.2 82.42 196.86 87.63 189.95\" style=\"fill:#fff;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m126.28 149.82c-6.16 2.43-15.52 2.42-21.56 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#1c1c1c;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m83.527 103.98v10h10v-10h-10zm53.945 0v10h10v-10h-10z\" style=\"fill:#1a1a1a;\"/><path d=\"m56.621 94.906v11.688h5.3418v6.4922h5.3418v6.1055h5.3223v6.2324h26.846v-6.2324h5.3047v-6.1055h5.1445v-6.0039h11.154v6.0039h5.1446v6.1055h5.3066v6.2324h26.846v-6.2324h5.3203v-6.1055h5.3438v-6.4922h5.3418v-11.688z\" style=\"fill:#1a1a1a;\"/><path d=\"m67.387 100.65v5.9394h5.1992v-5.9394zm5.1992 5.9394v6.4922h5.4238v-6.4922zm5.4238 0h5.1992v-5.9394h-5.1992zm5.1992 0v6.4922h5.4258v-6.4922zm5.4258 6.4922v6.1055h5.1426v-6.1055zm-10.625 0v6.1055h5.1445v-6.1055zm48.281-12.432v5.9394h5.1992v-5.9394zm5.1992 5.9394v6.4922h5.4238v-6.4922zm5.4238 0h5.1992v-5.9394h-5.1992zm5.1992 0v6.4922h5.4258v-6.4922zm5.4258 6.4922v6.1055h5.1426v-6.1055zm-10.625 0v6.1055h5.1445v-6.1055z\" style=\"fill:#fff;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m157.79 67.5a61.31 61.31 0 0 1-42.79 17.43h-55.7c18.16-37.74 68.27-46.85 98.49-17.43z\" style=\"fill:#4d4d4d;\"/><path d=\"m122.93 7.0078c-10.503-0.15729-21.09 1.6448-29.545 5.4316-17.141 7.8999-32.169 23.297-43.973 38.779-5.1703 6.8631-8.7779 13.46-8.1855 18.395 0.93114 12.312 10.372 26.483 11.068 36.9 15.663-72.081 105.99-70.452 124.91-7.0525l4e-3 0.0156c5.616-10.926 8.0682-20.188 8.352-27.653 0.43654-15.607-7.8088-21.149-21.735-28.249 1.7934-3.7704 1.7273-7.5023 2.0625-10.154-0.79964-7.8568-3.6796-13.51-10.43-17.758-5.9434-3.7404-13.06-6.0867-18.463-7.2266-4.5319-0.87895-9.2901-1.3562-14.064-1.4277z\" style=\"fill:#4d4d4d;\"/><path d=\"m42.426 75.338c0.52158 18.689 10.557 74.338-18.115 101.25 12.38 10.603 28.352 19.061 46.025 24.594 11.032-4.6874 22.88-7.4147 34.817-8.5046l0.0633-14.477c-22.49-4.3813-40.766-18.898-48.862-39.967-8.096-21.07-4.7931-44.72 9.2478-62.393zm124.67 2.7207c7.8997 10.886 11.743 24.64 11.787 37.441-0.36632 30.178-22.389 57.576-53.12 62.708l0.0238 14.471c12.282 1.1216 24.518 3.9888 35.825 8.9128 15.488-5.1448 30.007-13.325 42.396-25.043-13.136-22.051-23.282-63.045-18.694-101.55z\" style=\"fill:#4d4d4d;\"/><path d=\"m143.61 46.383c-11.639 0.12482-20.998 1.8906-20.998 1.8906l-9 3.5059c0.63003-0.0191 1.2603-0.0289 1.8906-0.0293h0.0996c35.169 0.055 60.959 27.235 63.283 63.383 7.4e-4 31.157-22.742 57.213-53.106 63.079l-0.0216 14.498c11.567 1.0563 23.154 3.6067 33.887 8.0463 35.952-15.315 55.082-52.303 36.709-68.279-5.018-7.9035-10.44-15.409-9.5544-23.03 5.0545-50.452 0.39626-63.561-43.189-63.064zm-69.966 21.09c-15.286 3.244-17.096 3.73-31.734 6.6953 3.0304 13.081 3.0583 22.274 1.2085 30.012-3.8004 11.361-8.9712 19.787-12.286 28.764-6.8823 22.459-2.9157 31.982 12.093 46.165 8.6595 8.0693 19.861 16.209 30.939 20.647 2.669-1.0316 5.3729-1.9628 8.106-2.792 7.4979-2.275 15.388-3.6535 23.206-4.3673l0.0433-14.393c-23.933-4.5937-44.283-21.98-50.77-45.817-6.3319-23.265 0.51104-48.752 19.195-64.914z\" style=\"fill:#4d4d4d;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
//Older 风格
|
||||
OlderStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5l15.71 15.75h21z\" style=\"fill:#666;\"/><path d=\"m89.291 195a114.79 114.79 0 0 0-38.002 16.5 115.53 115.53 0 0 0 38.002 16.482zm52.434 0v32.982a115.53 115.53 0 0 0 38-16.482 114.79 114.79 0 0 0-38-16.5z\" style=\"fill:#999;\"/><path d=\"m157.15 199.75c0.2548 7.4501 1.54 14.855 4.9512 21.432a115.53 115.53 0 0 0 17.619-9.6797 114.79 114.79 0 0 0-22.57-11.752zm-83.295 2e-3a114.79 114.79 0 0 0-22.57 11.75 115.53 115.53 0 0 0 17.621 9.6797c3.411-6.5765 4.6944-13.98 4.9492-21.43z\" style=\"fill:#ccc;\"/><path d=\"m99.197 204.97v2e-3l16.302 16.301 16.301-16.301v-2e-3z\" style=\"fill:#fff;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m100.19 152.09c2.8726 4.0616 9.8095 4.7232 15.119-0.45432 5.0656 4.5134 11.167 5.6898 15.495 0.31458\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.8949;stroke:#333;\"/><path d=\"m109.67 135.53c-0.9758 0.0743-2.05 0.45327-3.1485 0.99414-4.3235 2.1399-7.3862 4.2557-10.639 7.1406-0.6251 0.5715 0.1168 0.77785 1.4238 0.87304 5.6967 0.0536 14.384 0.41404 15.098-0.875 1.9251-2.0788 1.7969-5.3303-0.1816-7.3008-0.701-0.67533-1.5769-0.90632-2.5527-0.83203zm11.656 0c-0.9758-0.0743-1.8517 0.1567-2.5527 0.83203-1.9785 1.9705-2.1067 5.222-0.1817 7.3008 0.7142 1.289 9.401 0.9286 15.098 0.875 1.307-0.0952 2.0489-0.30154 1.4238-0.87304-3.2524-2.8849-6.3151-5.0007-10.639-7.1406-1.0985-0.54087-2.1727-0.91985-3.1485-0.99414z\" style=\"fill:#333;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m97.56 107.84a10.63 10.63 0 0 1-15 0.13l-0.13-0.13\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.3px;stroke:#000;\"/><path d=\"m148.59 107.84a10.63 10.63 0 0 1-15 0.13l-0.13-0.13\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.3px;stroke:#000;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m41.668 87.073c-9.2319-0.0231-11.63 6.5104 2.2676 17.66-14.015 1.1231-4.3662 16.457 4.875 24.66 4.0686 3.0199 6.4647 5.4657 5.5078 1.1348-1.2079-4.9178-1.8184-9.9634-1.8184-15.027 3.26e-4 -7.5692 1.2547-15.016 3.7883-22.183 0.57048-1.7876 1.0689-2.0306-0.37721-2.6839-5.5405-2.4478-10.375-3.5511-14.243-3.5608z\" style=\"fill:#ccc;\"/><path d=\"m185.48 89.513c-2.4418-0.11189-5.4618 0.81187-9.5148 3.2121-1.314 0.81729-0.70075 1.995-0.32301 3.2653 3.194 10.982 3.8215 22.462 1.2538 33.628-0.31613 1.688-0.47649 3.569 2.6953 1.3516 7.7016-5.371 19.17-18.734 16.918-26.105-1.4251-3.9177-11.4-0.35546-11.4-0.35546s4.987-4.2755 5.3437-9.6191c0.20048-3.0057-1.5237-5.2189-4.9726-5.377z\" style=\"fill:#ccc;\"/><path d=\"m91.689 36.108c-3.7298-7.3864-9.5859-10.504-17.578-6.7891-9.5194 4.5907-15.629 18.444-13.416 29.232 0 0-8.5511-4.9878-18.17-3.5625-19.623 8.094-1.4102 29.869 10.817 37.342 2.075 1.297 2.5792 1.7432 3.4291-0.37685 2.6746-6.5374 6.1886-12.722 11.297-17.709 4.1039 8.7427 14.629 4.1809 20.006-0.14062 4.4873 9.6838 10.377 6.3535 15.377 3.4785 4.0764 7.8829 10.756 7.25 17.631 0.0625 4.875 4.5625 14.713 4.1867 15.555-3.426 8.4753 2.6244 14.012 10.437 22.962-1.4764 8.8552 6.8221 14.407 16.853 17.122 27.51 0.34 1.554 1.175 0.85565 2.2212 0.44315 10.255-4.286 22.842-15.749 15.705-23.975-3.5623-3.5623-13.539-2.1387-13.539-2.1387s6.77-7.1233 9.2637-18.168c2.4936-11.043-23.514-4.9883-23.514-4.9883s7.4818-5.6993 12.113-13.537c4.6314-7.8378-2.4943-11.756-11.045-11.043-8.5496 0.71204-17.1 7.4805-17.1 7.4805s3.3946-7.8055-3.5625-12.826c-9.5935-6.9234-23.869 6.4121-23.869 6.4121-4.2562-26.835-24.872-6.386-31.707 8.1953z\" style=\"fill:#ccc;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Firehair 风格
|
||||
FirehairStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m116 203.13c-0.12 0-0.25 0.12-0.49 0.12s-0.25-0.12-0.49-0.12zm-27.29-8c0.87-0.25 1.72-0.47 2.56-0.69a32.37 32.37 0 0 0 0.3 8.57 21.5 21.5 0 0 0 7 6.88c6.41-6 16.8-6.64 16.8-6.64s10.5 0.58 17 6.69a21.61 21.61 0 0 0 6.93-6.66 32.34 32.34 0 0 0 0.35-8.84l2.13 0.56a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.64 114.64 0 0 1 37.38-16.37z\" style=\"fill:#e9e9e9;\"/><path d=\"m126.15 206-3.92 7.83h-13.46l-3.92-7.83a36.59 36.59 0 0 1 10.65-2.7 35.66 35.66 0 0 1 10.65 2.7z\" style=\"fill:#818181;\"/><path d=\"m124.54 230.65-2.18-16.74h-13.47l-2.19 16.76c2.9 0.22 5.84 0.33 8.8 0.33s6.06-0.12 9-0.35z\" style=\"fill:#989898;\"/><path d=\"m134.84 186s0.86 9.8-19.34 17.26c0 0 15.79 0.86 20.57 11.76 0.12 0.49 9.3-23.26-1.23-29z\" style=\"fill:#fff;\"/><path d=\"m96.16 186c-10.41 5.76-1.35 29.39-1.1 29 4.65-10.78 20.56-11.76 20.56-11.76-20.32-7.45-19.46-17.24-19.46-17.24z\" style=\"fill:#fff;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m118.57 165.14a8.66 8.66 0 0 0-2.76-4.23h-0.62a8 8 0 0 0-2.76 4.22c-0.52 1.89 2.07 10.61 2.76 12.53h0.62c0.64-1.76 3.19-10.82 2.76-12.52z\" style=\"fill:#333;\"/><path d=\"m102.81 152.24a2.4921 2.4921 0 1 1 1.19-4.84l0.21 0.06a37.1 37.1 0 0 0 5.43 1.12 44.52 44.52 0 0 0 11.76 0 37.1 37.1 0 0 0 5.43-1.12 2.4903 2.4903 0 0 1 1.59 4.72l-0.21 0.06a43.08 43.08 0 0 1-6.15 1.29 48.55 48.55 0 0 1-13.08 0 42.79 42.79 0 0 1-6.17-1.29z\" style=\"fill:#333;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m86.851 100.39a4.94 4.94 0 1 0 4.9297 5 5 5 0 0 0-4.9297-5zm57.221 0a4.94 4.94 0 1 0 4.9394 4.9394 4.94 4.94 0 0 0-4.9394-4.9394z\" style=\"fill:#333;\"/><path d=\"m86.207 89.365c-25.504 0-21.503 6.8561-21.035 19.596 0.80177 18.121 17.763 16.514 21.201 16.639 14.758-0.041 20.518-8.227 22.951-22.932 1.8166-10.731-9.251-13.174-23.117-13.303zm58.598 0c-13.866 0.1284-24.936 2.5717-23.119 13.303 2.4332 14.705 8.1936 22.891 22.951 22.932 3.4383-0.125 20.399 1.4828 21.201-16.639 0-18.965-0.47958-19.596-21.033-19.596z\" style=\"fill:#4d4d4d;\"/><path d=\"m169.87 90.255a0.51 0.51 0 0 0-0.43991-0.52 167.64 167.64 0 0 0-22.6-1.6801c-12 0-27.47 3.7601-30.17 3.7601h-2.4c-1.2499 0-5.29-0.80996-10.45-1.6801a124.35 124.35 0 0 0-19.72-2.08 166.18 166.18 0 0 0-19.31 1.24c-1.56 0.17999-2.69 0.35009-3.2899 0.44009a0.51 0.51 0 0 0-0.44007 0.52l-0.091 6.4501a0.57 0.57 0 0 0 0.33012 0.52l0.73994 0.23992c1.08 0.41992 1.0001 19.85 6.78 24.71 3.4401 2.8599 6.51 4.4899 19.42 4.4899 7.4699 0 12.17-1.9999 16.63-8 3.21-4.32 6.0999-14.55 6.0999-14.55 0.82006-4.07 3.7702-4.52 4.43-4.5801h0.12068c0.11078 0 3.66 0.0593 4.57 4.5801 0 0 2.8599 10.22 6.0699 14.54 4.4601 5.9999 9.1601 8 16.63 8 12.91 0 16-1.63 19.42-4.4901 5.7898-4.86 5.6998-24.29 6.78-24.71l0.73994-0.23993a0.57 0.57 0 0 0 0.32996-0.52l-0.12068-6.4501zm-65 23c-1.9101 4.5-6.8 10.29-13.7 10.64-20.7 0.99985-21.65-4.7401-23-9.3201a31.45 31.45 0 0 1-1.2099-13.18c0.53997-4.5799 1.7-7.2699 3.7801-8.6201a9.3 9.3 0 0 1 4.3499-1.51 85.07 85.07 0 0 1 11.4-0.52 59.23 59.23 0 0 1 9.2099 0.69999c7.37 1.2 12.35 3.7001 12.35 6.1601a46.12 46.12 0 0 1-3.23 15.64zm58 1.3201c-1.34 4.5799-2.29 10.36-23 9.3201-6.91-0.3501-11.81-6.1401-13.71-10.64a46.35 46.35 0 0 1-3.22-15.64c0-3.39 9.43-6.8599 21.56-6.8599 12.13 0 14 0.89996 15.75 1.9999 2.08 1.3502 3.2398 4 3.77 8.6201a31.23 31.23 0 0 1-1.1601 13.17z\" style=\"fill:#333;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m156.1 15.879c-0.38556 5.3015-1.7049 9.4762-3.6602 12.76-0.41226 23.773-9.2343 35.229-15.154 42.797l15.062-4.6641c-0.66253 2.8135-2.4628 7.156-0.34766 12.137 1.6334-2.3144 7.9395-5.807 13-3.3477-0.43442 3.5532-0.95271 7.094-1.4512 10.639l8.9648 0.85937c0.83453 3.8792 0.51719 9.3449-0.59961 11.736l5.5508 2.0098c0.20764 2.7646 0.10001 5.4906-0.74609 8.875 8.4545-1.7225 14.213-4.3896 19.641-13.188 2.8639-4.7524 4.9018-10.483 4.7305-17.242-4.1612 4.916-9.6484 7.2485-15.26 10.109 6.507-11.065 8.8648-22.768 8.1367-30.58-7.3456 10.251-11.649 13.06-19.918 16.9 1.2386-11.4 5.5249-18.582 12.461-27.27-11.392-1.3025-16.301 1.4749-24.891 6.4395 4.5466-14.036 2.2208-26.679-5.5195-38.971zm-117.76 28.682c9.3378 3.6366 19.581 9.0234 21.129 18.549-7.6182 0.0414-14.897-3.5072-20.242-7.1894-0.15967 8.2309 2.8451 12.252 6.7734 19.08-7.2127 1.6129-12.084 4.8315-17.471 9.4805 7.2948-0.15715 12.299-1.0502 16.891 4.2793-6.0512 5.0164-11.99 10.79-11.99 19.24 9.257-6.1688 12.495-5.9486 21.137-2.2012 1.2906-8.0996 2.3978-14.872 2.7869-16.435 2.4719-0.73247 3.5247-0.94807 5.9221-1.2938-2.1556-7.4281 1.0996-9.5176 2.4141-11.6l7.543 1.5059c-3.9093-6.1699 2.6565-12.483 7.1445-15.51-4.4474-7.2082-5.6649-11.558-7.377-16.797-11.198-8.2947-23.895-6.2742-34.66-1.1094z\" style=\"fill:#f9f9f9;\"/><path d=\"m101.9 7.6408c-10.047 6.2416-12.441 28.646-12.131 33.289-6.9249-5.8258-7.8992-13.75-7.7695-19.203-9.6235 6.0158-10.666 14.421-9 23.943 1.1061 5.1411 2.3972 10.461 7.377 16.797 2e-3 -1e-3 4e-3 -3e-3 6e-3 -4e-3 2.7742 2.8742 5.4644 5.5941 8.3477 8.3574 0.41187-6.971 0.45449-13.622 7.1856-15.824 3.9532 2.8169 7.4123 5.9388 11.084 9.1035l10.559-10.25c5.6447 3.961 5.4531 6.5652 6.5215 14.104 2.153-1.7546 8.719-9.0037 15.844-10.139 0.98706 4.1261-0.99388 10.308-2.6387 13.621 0 0 14.32-11.846 15.195-27.971 0.33968-6.2599 0.2237-11.146-0.041-14.826-3.2125 5.5652-8.7118 8.7799-13.789 10.15-4.2715-9.2486-2.4785-21.435-0.48047-29.309-12.21 3.0195-20.932 18.337-22.172 25.07-9.2678-7.397-13.605-16.146-14.098-26.91z\" style=\"fill:#f9f9f9;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
// Blond 风格
|
||||
BlondStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5l26.23 13 26.27-13z\" style=\"fill:#131111;\"/><polygon points=\"115.5 208.03 115.5 207.74 82.72 188.91 80.45 198.86 101.46 222.72\" style=\"fill:#cbcbcb;\"/><polygon points=\"115.5 208.03 115.5 207.74 148.28 188.91 150.55 198.86 129.54 222.72\" style=\"fill:#cbcbcb;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m123.07 154.05a10.61 10.61 0 0 1-15 0.14l-0.14-0.14\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.3px;stroke:#000;\"/><path d=\"m120.1 142.22 0.19-0.11c3-1.87 5.45-2.4 7.3-1.46 2.15 1.1 3.12 3.84 4.84 5.5a5.18 5.18 0 0 0 6.68 0.73m-28.21-4.66-0.19-0.11c-3-1.87-5.45-2.4-7.3-1.46-2.15 1.1-3.12 3.84-4.84 5.5a5.18 5.18 0 0 1-6.68 0.73\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:5.9998px;stroke:#4d4d4d;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m161.73 86.016h-92.51c-3.37 0-6.0001 2.3998-6.0001 5.2999v28.45c0 3.0002 2.74 5.3001 6.0001 5.3001h32.36c7.0901 0 7.44-19.43 13.82-19.43s6.8801 19.44 13.83 19.44h32.36c3.37 0 5.9999-2.4 5.9999-5.3001v-28.46c0.14043-2.9001-2.6-5.2999-5.9-5.2999z\" style=\"fill:#8f8f8f;\"/><path d=\"m161.73 86.016h-92.51c-3.37 0-6.0001 2.3998-6.0001 5.2999v28.45l104.55-28.45c0-2.9001-2.74-5.2999-5.9999-5.2999z\" style=\"fill:#e3e3e3;\"/><path d=\"m161.73 86.016h-92.51c-3.37 0-6.0001 2.3998-6.0001 5.2999v28.45c0 3.0002 2.74 5.3001 6.0001 5.3001h32.36c7.0901 0 7.44-19.43 13.82-19.43s6.8801 19.44 13.83 19.44h32.36c3.37 0 5.9999-2.4 5.9999-5.3001v-28.46c0.14043-2.9001-2.6-5.2999-5.9-5.2999z\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4.0026px;stroke:#232323;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m69.834 33.826c-8.2001-0.0626-16.444 2.6753-23.152 7.7038-8.5298 6.9899-12.159 19.61-12.329 32.68-0.2041 15.476 1.6092 34.752 1.7464 51.915 0.10414 13.047 0.53485 25.984-2.9197 33.995-2.4994 5.81-9.0955 9.6006-16.196 12.311 7.9599 2.8301 25.009 2.8094 33.58 1.5393 10.8-1.59 17.238-6.5294 17.159-22.699-0.0911-15.93-1.3894-29.23-1.559-45.83-0.3208-11.983-1.569-24.291 4.9774-33.987 4.2139-6.1265 10.452-10.521 17.116-13.588 3.9292-1.8575 8.0384-3.3083 12.263-4.3297-6.8718-13.574-18.732-19.618-30.687-19.709z\" style=\"fill:#b3b3b3;\"/><path d=\"m90.8 76.246c11.918-17.125 31.996-23.218 49.743-17.488 11.81 3.9496 20.692 13.389 22.313 28.237 0.51051 6.2098 0.63413 12.445 0.37007 18.67-0.23973 11.2-0.72946 23.82-1.0995 34.08-0.82005 22.43 0.0593 35.1 24.589 36.3 8.5635 0.32122 17.137-0.22845 25.59-1.6405h-0.0198c-10.74-3.3799-17.98-15.609-19.3-26.289-1.29-10.41-0.6098-23.43-0.7898-38.091-0.1701-14.96 1.0398-29.819 0.28008-42.089-1.414-22.777-14.947-38.505-34.126-45.152-27.813-7.35-51.083 0.091-61.672 17.343-5.4698 8.9112-7.7413 20.07-5.8788 36.121z\" style=\"fill:#b3b3b3;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Ateam 风格
|
||||
AteamStyle: {
|
||||
TypeClo: "<path id='clo' d=\"M61.11,205.59l3.49,3.69-6.26,6.6A115.45,115.45,0,0,0,72,222.51v-22a115.19,115.19,0,0,0-10.85,5.1Z\" style=\"fill:#eee;\"/><path d=\"M93.24,228.85V199l-4-4A114.43,114.43,0,0,0,72,200.49v22a114.43,114.43,0,0,0,21.28,6.34Z\" style=\"fill:#787878;\"/><path d=\"m159 222.51v-22a114.63 114.63 0 0 0-17.25-5.51l-4 4v29.86a114.16 114.16 0 0 0 21.25-6.35z\" style=\"fill:#787878;\"/><path d=\"m169.89 205.59-3.49 3.69 6.26 6.6a115.45 115.45 0 0 1-13.66 6.63v-22a115.19 115.19 0 0 1 10.85 5.1z\" style=\"fill:#eee;\"/><path d=\"M115.5,219.62A28.5,28.5,0,0,1,87.25,195c2.93-.74,5.92-1.36,8.94-1.87a19.41,19.41,0,0,0,38.62,0c3,.51,6,1.13,8.94,1.87a28.49,28.49,0,0,1-28.25,24.63Z\" style=\"fill:#c9c9c9;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m115.5 153.93a14 14 0 0 1-10.5-4.69 3.4209 3.4209 0 0 1 5-4.67l0.08 0.08 0.08 0.09a7.35 7.35 0 0 0 10.39 0.37l0.37-0.37a3.4206 3.4206 0 1 1 5.23 4.41l-0.08 0.09a14 14 0 0 1-10.53 4.69z\" /><path d=\"m115.27 127.32c-7.6627-0.03-15.251 1.4419-20.646 5.1465-7.62 5.33-9.9053 11.512-14.127 18.109-3.4379 5.2447-9.326 10.024-13.467 6.334 25.425 29.755 71.409 29.786 96.875 0.0664-6.8104 3.9305-11.545-2.47-13.508-6.4004-10.697-17.605-14.115-22.656-35.127-23.256zm-0.26758 8.3984c7.457 0.0802 14.986 1.2966 17.146 5.9522 2.5765 11.319-7.5878 17.454-16.681 17.515-6.09-0.05-12.2-2.3802-15.26-7.7402-6.36-11.16 3.6349-15.607 14.795-15.727z\" style=\"fill:#404040;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m91.72 97.36v11.4m47.56-11.4v11.4\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:7.9999px;stroke:#333;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m52.107 57.293c-1.3411 14.839-3.8707 52.771 1.3145 72.715-0.67572-43.829 12.389-70.177 62.078-70.187 49.689 0.010061 62.754 26.359 62.078 70.187 5.1852-19.944 2.6556-57.876 1.3145-72.715h-63.393-63.393z\" style=\"fill:#4d4d4d;\"/><path d=\"m52.339 30.629c-1.3825 24.448-2.1216 45.905-1.4497 66.517 9.4643-48.304 112.77-54.916 129.22 0 0.67191-20.612-0.3798-47.256-1.4928-66.517-32.241 14.296-91.346 18.861-126.28 0z\" style=\"fill:#4d4d4d;\"/><path d=\"m115.5 24.92c-22.25 0-44.5 4.2296-56.72 12.69-3.32 2.3-5.0602 6.4392-5.5903 10.269-0.45275 3.23-0.84043 6.7561-1.1785 10.461h126.98c-0.33704-3.7047-0.72492-7.2306-1.1775-10.461-0.53009-3.8301-2.2697-7.9992-5.5897-10.269-12.22-8.4601-34.47-12.69-56.72-12.69z\" style=\"fill:#4d4d4d;\"/><path d=\"m76.521 39.139c21.233 3.3965 33.116-13.392 37.59-31.72 4.3614 17.158 14.175 34.968 36.577 31.584-33.921 20.594-57.646 11.594-74.167 0.1345z\" style=\"fill:#4d4d4d;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
// Rasta 风格
|
||||
RastaStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m91.92 194.41a101.47 101.47 0 0 1 23.58 17.09 101.47 101.47 0 0 1 23.58-17.09c0.89 0.19 1.78 0.38 2.67 0.59a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5c0.88-0.21 1.78-0.4 2.67-0.59z\" style=\"fill:#757575;\"/><path d=\"m73.65 199.82c16.59 8.23 28.72 18.91 34.27 30.93a114.86 114.86 0 0 1-56.65-19.25 115.06 115.06 0 0 1 22.38-11.68z\" style=\"fill:#d8d8d8;\"/><path d=\"m60.63 205.85c12.35 5.94 21.93 13.44 27.59 21.91a114.7 114.7 0 0 1-36.95-16.26q4.53-3 9.36-5.65z\" style=\"fill:#757575;\"/><path d=\"m157.35 199.82c-16.6 8.23-28.72 18.91-34.27 30.93a114.86 114.86 0 0 0 56.65-19.25 115.06 115.06 0 0 0-22.38-11.68z\" style=\"fill:#d8d8d8;\"/><path d=\"m170.37 205.85c-12.35 5.94-21.93 13.44-27.59 21.91a114.7 114.7 0 0 0 36.95-16.26q-4.53-3-9.36-5.65z\" style=\"fill:#757575;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m115.5 131c-17.71 0.65-27 9.41-29.61 23.69-1 5.62-0.43 7.06 2.76 7.17 22.76 0.76 22.23 18.21 26.85 18.89 4.62-0.68 4.09-18.13 26.85-18.89 3.19-0.11 3.79-1.55 2.76-7.17-2.62-14.28-11.9-23-29.61-23.69zm0 29.31c-10 0-18-5-18-11.17s8.08-11.17 18-11.17 18 5 18 11.17-8.08 11.17-18 11.17z\" style=\"fill:#333;\"/><path d=\"m123.54 148.46a11.53 11.53 0 0 1-16.09 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:6.7998px;stroke:#000;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m133 108.17h14.17m-63.26 0h14.09m-20.69-8.93a21.31 21.31 0 0 1 27.29 0m21.8 0a21.31 21.31 0 0 1 27.29 0\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:4.8243px;stroke:#000;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m115.5 51.75c-38.702 5.3101-54.215 18.038-59.863 35.101\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m115.5 51.75c-7.8393 3.6337-5.5974 16.583-14.341 23.452\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m111.35 48.614c-22.634-6.9181-42.457-3.1988-55.733 2.5105\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m115.47 54.008c0.1965-6.7774-0.1436-26.309 0.05-38.184\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m68.874 28.177c34.115-3.382 41.987 13.321 45.17 19.602\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m116.49 48.69c2.8876-6.3019 10.358-21.518 43.469-22.326\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m116.92 51.766c1.5094 6.3991 3.4988 15.595 10.088 23.058\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m113.81 51.532c22.03-7.8674 46.709-7.3614 59.444-2.0465\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m114.53 52.278c36.226 4.8583 52.414 17.092 59.373 33.347\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m55.637 86.851c-4.1213 12.452-2.9877 27.213-1.777 43.084\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m55.614 51.124c-13.422 5.5019-21.908 16.409-24.712 28.774-1.8322 8.4632-1.9809 18.156-1.6096 28.486\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m173.26 49.486c24.917 10.399 26.707 36.537 27.209 59.62\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m173.9 85.625c5.4042 12.625 5.2413 27.675 4.5745 43.58\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m53.86 129.93c1.293 16.951 2.6738 35.169-2.1664 53.193\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m29.292 108.38c0.6173 17.177 2.6722 36.119 0.8158 54.108\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m200.47 109.11c0.3586 18.529-1.2751 36.94 1.9231 48.985\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/><path d=\"m178.48 129.2c-0.7279 17.362-2.0563 35.743 2.6011 53.099\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:12;stroke:#333;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
// Meta 风格
|
||||
MetaStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m141.75 195a114.79 114.79 0 0 1 38 16.5 115.53 115.53 0 0 1-128.46 0 114.79 114.79 0 0 1 38-16.5 115.77 115.77 0 0 1 15.71-2.53v-14.09a63.8 63.8 0 0 0 21 0v14.09a116.6 116.6 0 0 1 15.75 2.53z\" style=\"fill:#1a1a1a;\"/><path d=\"m60.984 205.66 6.2675 2.2051 3.4074-6.819 2.8018-1.1353-3.9911 7.9907 27.222-3.0857 3.2541-11.739 2.1451-0.2692-3.2833 11.819 20.393-1.6011-14.191-15.945v-2.4379l17.606-5.7274 3.3855-0.473v1.47l-19.167 6.2295 14.731 16.542 19.839-7.7432 3.3636 0.8223-21.371 8.34 20.532 13.842 2.6777-21.687 1.9481 0.5604-2.7726 22.378 0.0584 0.0364 8.5075 4.9923-2.4807 0.85145-6.4718-3.7916-1.2987 6.0622-2.1524 0.53125 1.3425-6.2804-17.037 8.8348-5.0271 0.35661 21.59-11.193-20.962-14.133-7.5006 25.457-2.0721-0.0364 7.6392-25.915-21.05 1.652 9.0109 24.052-1.4155-0.0946-0.49615-0.0437-0.073-7e-3 -0.2043-0.0145-8.3688-22.342-10.127 19.242-1.9846-0.52399 10.514-19.962-26.04 2.9547 13.425 16.418-3.4438-1.0625-12.083-14.781-8.1645 5.9675-1.9043-1.077 8.128-5.9385-6.9898-2.4598 2.3348-1.2881zm92.509-7.2556 14.228 20.093-1.8095 0.89514-15.614-22.043z\" style=\"fill:#b2b2b2;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m97.06 144.59a20.15 20.15 0 0 0 36.88 4.53z\" style=\"fill:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.9999px;stroke:#000;\"/>",
|
||||
TypeEyes: "<line id='eyes' x1=\"85.29\" x2=\"85.29\" y1=\"98.73\" y2=\"109.79\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:8.7999px;stroke:#000;\"/><path d=\"m108.28 72.16h62.18c9.19 0 13.32 1.21 14.71 8.52 3.61 18.95 2.2 33.49-0.44 43.75a65.07 65.07 0 0 1-5.89 14.78 73.52 73.52 0 0 1-7.06 10.26c-1.8 2.27-5.17 1.21-4.19-1.09 0.14-0.47 0.27-1 0.4-1.48a14.29 14.29 0 0 0 0.52-6.62 12.52 12.52 0 0 0-3.88-6.3c-4.17-3.9-12.81-8.71-32.53-13.66-6.4-1.6-10.69-2.24-11.76-2.79a7.08 7.08 0 0 1-3.85-6.31v-9c0-2.39 0.18-4.55-1.56-6.57s-4.16-2.13-6.65-2.14a6 6 0 0 1-6-6v-9.35a6 6 0 0 1 6-6z\" style=\"fill:#1a1a1a;\"/><path d=\"m135.9 98.73v9.27m15.22-9.29v9.29\" style=\"fill:transparent;stroke-linecap:round;stroke-linejoin:round;stroke-width:7.7998px;stroke:#b2b2b2;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m109.99 15.57c-13.46 3.6301-19.789 11.95-24.069 24.08-6.9996-7-8.7307-10.82-7.5606-21.43a41 41 0 0 0-9.2698 24.988c0.0366 7.6776 5.6462 13.939 12.697 15.297-13.315 5.8106-15.258 22.033-14.045 33.524 5.7687-11.861 14.254-20.981 27.258-22.951-0.43017 6.6-2.5099 10.22-7.29 17.66 18.29-2.8601 25.119-7.8199 37.15-18.24 0.46001 0 1.0001 0.089 1.4606 0.12058-0.33023 3.5601-1.0906 6.5598-5.0004 12.46 9.5298-1.32 14.721-5.8006 17.539-11.671 8.8862 0.95314 15.836 6.785 21.26 14.818 1.928-15.211-4.4766-26.6-19.807-34.036 1.4167-2.6974 8.0143-11.925 17.661-15.721-1.424-0.28569-2.8883-0.49486-4.4033-0.61125-5.71-0.41992-13.62-0.99982-24.89 4.1703 2.8501-8.5101 10.21-11 18.05-13.12-15.131-1.2501-28.61-2.5898-40.53 8.1801-1.8997-6.21-0.18055-12.54 3.7889-17.52z\" style=\"fill:#111;\"/><path d=\"m172.63 69.954c1.2292 14.064 0.93841 29.96 0.34635 45.169 1.7887 6.796 3.0379 13.235 3.8842 18.388l0.13973-0.011c1.0001 6.56 2.3597 13.18 3.2698 19.73 2.0002-6.5699 2.5303-18.25 3.2405-25.43 1.2597-13 1.8296-29.311-0.43017-41.931-0.85041-4.72-2.0007-7.6896-2.0007-8.4796 4.6205 3.5601 8.6606 9.2204 13.001 14.15-0.6751-3.4318-1.347-6.6004-2.0567-9.5273-4.047-5.7183-13.726-12.154-19.393-12.06z\" style=\"fill:#111;\"/><path d=\"m157.97 34.471c-10.339 2.7579-17.715 13.543-19.132 16.24 15.33 7.4361 20.783 17.96 21.278 33.517 5.9534 8.8179 10.066 20.289 12.857 30.895 0.87636-13.178 1.8186-27.726 0.26566-44.28 2.5698 0.44857 9.1372 1.3934 18.781 11.17-2.1158-8.7321-4.5671-15.31-8.4539-20.283-4.5598-5.8401-10.999-10.431-23.809-13 9.6502-3.34 16.27-0.76993 25.5 2.1301-8.1388-7.4315-16.474-14.219-27.287-16.389z\" style=\"fill:#111;\"/><path d=\"m61.473 73.354c-7.256-0.77501-13.024 2.3746-16.262 5.3879 0.73789-0.45409 1.3868-0.74208 1.8489-0.74208 0 0-1.5198 10.359-1.6197 11.519-1.56 19.73 0.99957 43.401 6.37 62.471 1.3099 4.6899 1.1895 3.0893 1.8898-0.9107 1.7526-10.061 3.3891-24.703 6.9739-38.864-5.068-17.627-4.2508-32.403 0.79937-38.861z\" style=\"fill:#111;\"/><path d=\"m69.09 43.21c-0.0253 1.0803-8e-3 2.1612 0.0523 3.2402-3.8402 0-12.46 0.71984-16 2.1598-4.4504 1.8001-8.48 5.4801-11.67 11.83 7.2999-3.94 11.899-3.8502 16.66-1.8102-10.39 3.45-19.52 11.37-20.32 26.9 1.1456-1.5053 4.6079-4.9789 7.1393-6.6285 0.09-0.0587 0.17427-0.10556 0.26167-0.15946 3.7141-2.3211 9.0494-5.1247 15.181-4.9553-5.0501 6.4577-6.6824 20.434 0.28207 38.428 1.7866-7.0567 4.0574-13.994 7.0681-20.184-1e-3 -11.664 2.0764-27.774 15.391-33.585-7.0508-2.1538-12.709-7.991-14.043-15.236z\" style=\"fill:#111;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m115.5 51.75a63.75 63.75 0 0 0-10.5 126.63v14.09a115.5 115.5 0 0 0-53.729 19.027 115.5 115.5 0 0 0 128.46 0 115.5 115.5 0 0 0-53.729-19.029v-14.084a63.75 63.75 0 0 0 53.25-62.881 63.75 63.75 0 0 0-63.65-63.75 63.75 63.75 0 0 0-0.09961 0z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"M33.83,33.83a115.5,115.5,0,1,1,0,163.34,115.49,115.49,0,0,1,0-163.34Z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
|
||||
// Square 风格
|
||||
SquareStyle: {
|
||||
TypeClo: "<path id='clo' d=\"m50 140h130v40h-130z\" style=\"fill:#666;\"/>",
|
||||
TypeMouth: "<path id='mouth' d=\"m97.06 144.59a20.15 20.15 0 0 0 36.88 4.53z\" style=\"fill:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.9999px;stroke:#000;\"/>",
|
||||
TypeEyes: "<path id='eyes' d=\"m85 95h20v20h-20z m40 0h20v20h-20z\" style=\"fill:#000;\"/>",
|
||||
TypeTop: "<path id='top' d=\"m60 40h110v40h-110z\" style=\"fill:#333;\"/>",
|
||||
TypeHead: "<path id='head' d=\"m50 50h130v130h-130z\" style=\"fill:#000;\"/>",
|
||||
TypeEnv: "<path id='env' d=\"m30 30h170v170h-170z\" style=\"fill:#01;\"/>",
|
||||
},
|
||||
}
|
||||
|
||||
// initShapes 初始化形状数据
|
||||
func (m *Manager) initShapes() {
|
||||
for _, style := range []StyleType{
|
||||
RoboStyle,
|
||||
GirlStyle,
|
||||
BlondeStyle,
|
||||
GuyStyle,
|
||||
CountryStyle,
|
||||
GeeknotStyle,
|
||||
AsianStyle,
|
||||
PunkStyle,
|
||||
AfrohairStyle,
|
||||
NormieFemaleStyle,
|
||||
OlderStyle,
|
||||
FirehairStyle,
|
||||
BlondStyle,
|
||||
AteamStyle,
|
||||
RastaStyle,
|
||||
MetaStyle,
|
||||
SquareStyle,
|
||||
} {
|
||||
if styleSet, exists := defaultStyleSet[style]; exists {
|
||||
m.AddStyleSet(styleSet)
|
||||
}
|
||||
}
|
||||
}
|
115
style/style.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package style
|
||||
|
||||
import "github.com/landaiqing/go-pixelnebula/errors"
|
||||
|
||||
// ShapeType 表示形状类型
|
||||
type ShapeType string
|
||||
|
||||
// 预定义形状类型
|
||||
const (
|
||||
TypeClo ShapeType = "clo" // 衣服
|
||||
TypeMouth ShapeType = "mouth" // 嘴巴
|
||||
TypeEyes ShapeType = "eyes" // 眼睛
|
||||
TypeTop ShapeType = "top" // 头顶
|
||||
TypeHead ShapeType = "head" // 头部
|
||||
TypeEnv ShapeType = "env" // 环境/背景
|
||||
)
|
||||
|
||||
// 预定义风格类型常量
|
||||
const (
|
||||
RoboStyle StyleType = "robo"
|
||||
GirlStyle StyleType = "girl"
|
||||
BlondeStyle StyleType = "blonde"
|
||||
GuyStyle StyleType = "guy"
|
||||
CountryStyle StyleType = "country"
|
||||
GeeknotStyle StyleType = "geeknot"
|
||||
AsianStyle StyleType = "asian"
|
||||
PunkStyle StyleType = "punk"
|
||||
AfrohairStyle StyleType = "afrohair"
|
||||
NormieFemaleStyle StyleType = "normiefemale"
|
||||
OlderStyle StyleType = "older"
|
||||
FirehairStyle StyleType = "firehair"
|
||||
BlondStyle StyleType = "blond"
|
||||
AteamStyle StyleType = "ateam"
|
||||
RastaStyle StyleType = "rasta"
|
||||
MetaStyle StyleType = "meta"
|
||||
SquareStyle StyleType = "square"
|
||||
)
|
||||
|
||||
// StyleType 表示风格类型
|
||||
type StyleType string
|
||||
|
||||
// StyleSet 表示一组形状
|
||||
type StyleSet map[ShapeType]string
|
||||
|
||||
// Manager 形状管理器,负责管理所有形状
|
||||
type Manager struct {
|
||||
styleSets []StyleSet
|
||||
}
|
||||
|
||||
// NewShapeManager 创建一个新的形状管理器
|
||||
func NewShapeManager() *Manager {
|
||||
m := &Manager{}
|
||||
m.initShapes()
|
||||
return m
|
||||
}
|
||||
|
||||
// GetShape 获取指定索引和类型的形状
|
||||
func (m *Manager) GetShape(setIndex int, shapeType ShapeType) (string, error) {
|
||||
if setIndex < 0 || setIndex >= len(m.styleSets) {
|
||||
return "", errors.ErrInvalidShapeSetIndex
|
||||
}
|
||||
|
||||
shapeSet := m.styleSets[setIndex]
|
||||
shape, ok := shapeSet[shapeType]
|
||||
if !ok {
|
||||
return "", errors.ErrInvalidShapeType
|
||||
}
|
||||
|
||||
return shape, nil
|
||||
}
|
||||
|
||||
// StyleSetCount 返回形状集合数量
|
||||
func (m *Manager) StyleSetCount() int {
|
||||
return len(m.styleSets)
|
||||
}
|
||||
|
||||
// AddStyleSet 添加一个新形状集合
|
||||
func (m *Manager) AddStyleSet(shapeSet StyleSet) int {
|
||||
m.styleSets = append(m.styleSets, shapeSet)
|
||||
return len(m.styleSets) - 1
|
||||
}
|
||||
|
||||
// CustomizeStyle 自定义风格
|
||||
func (m *Manager) CustomizeStyle(styleSets []StyleSet) {
|
||||
m.styleSets = styleSets
|
||||
}
|
||||
|
||||
// GetStyleIndex 根据风格类型获取对应的索引值
|
||||
func (m *Manager) GetStyleIndex(style StyleType) (int, error) {
|
||||
// 遍历已初始化的风格列表获取索引
|
||||
for i, s := range []StyleType{
|
||||
RoboStyle,
|
||||
GirlStyle,
|
||||
BlondeStyle,
|
||||
GuyStyle,
|
||||
CountryStyle,
|
||||
GeeknotStyle,
|
||||
AsianStyle,
|
||||
PunkStyle,
|
||||
AfrohairStyle,
|
||||
NormieFemaleStyle,
|
||||
OlderStyle,
|
||||
FirehairStyle,
|
||||
BlondStyle,
|
||||
AteamStyle,
|
||||
RastaStyle,
|
||||
MetaStyle,
|
||||
SquareStyle,
|
||||
} {
|
||||
if s == style {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return -1, errors.ErrInvalidStyleName
|
||||
}
|
515
theme/init.go
Normal file
@@ -0,0 +1,515 @@
|
||||
package theme
|
||||
|
||||
import "github.com/landaiqing/go-pixelnebula/style"
|
||||
|
||||
var defaultThemeSet = map[style.StyleType]Theme{
|
||||
style.RoboStyle: {
|
||||
ThemePart{
|
||||
"env": {"ff2f2b"},
|
||||
"clo": {"fff", "000"},
|
||||
"head": {"fff"},
|
||||
"mouth": {"fff", "000", "000"},
|
||||
"eyes": {"000", "none", "0ff"},
|
||||
"top": {"fff", "fff"},
|
||||
},
|
||||
// 第二部分
|
||||
ThemePart{
|
||||
"env": {"ff1ec1"},
|
||||
"clo": {"000", "fff"},
|
||||
"head": {"ffc1c1"},
|
||||
"mouth": {"fff", "000", "000"},
|
||||
"eyes": {"FF2D00", "fff", "none"},
|
||||
"top": {"a21d00", "fff"},
|
||||
},
|
||||
// 第三部分
|
||||
ThemePart{
|
||||
"env": {"0079b1"},
|
||||
"clo": {"0e00b1", "d1fffe"},
|
||||
"head": {"f5aa77"},
|
||||
"mouth": {"fff", "000", "000"},
|
||||
"eyes": {"0c00de", "fff", "none"},
|
||||
"top": {"acfffd", "acfffd"},
|
||||
},
|
||||
},
|
||||
|
||||
// 创建Girl主题
|
||||
style.GirlStyle: {
|
||||
// 第一部分
|
||||
ThemePart{
|
||||
"env": {"a50000"},
|
||||
"clo": {"f06", "8e0039"},
|
||||
"head": {"85492C"},
|
||||
"mouth": {"000"},
|
||||
"eyes": {"000", "ff9809"},
|
||||
"top": {"ff9809", "ff9809", "none", "none"},
|
||||
},
|
||||
// 第二部分
|
||||
ThemePart{
|
||||
"env": {"40E83B"},
|
||||
"clo": {"00650b", "62ce5a"},
|
||||
"head": {"f7c1a6"},
|
||||
"mouth": {"6e1c1c"},
|
||||
"eyes": {"000", "ff833b"},
|
||||
"top": {"67FFCC", "none", "none", "ecff3b"},
|
||||
},
|
||||
// 第三部分
|
||||
ThemePart{
|
||||
"env": {"ff2c2c"},
|
||||
"clo": {"fff", "000"},
|
||||
"head": {"ffce8b"},
|
||||
"mouth": {"000"},
|
||||
"eyes": {"000", "ff9809"},
|
||||
"top": {"ff9809", "ff9809", "none", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// 创建Blonde主题
|
||||
style.BlondeStyle: {
|
||||
// 第一部分
|
||||
ThemePart{
|
||||
"env": {"00aad4"},
|
||||
"clo": {"fff", "000"},
|
||||
"head": {"ffe0bd"},
|
||||
"mouth": {"ff9a84"},
|
||||
"eyes": {"000", "fff"},
|
||||
"top": {"fff200", "fff200"},
|
||||
},
|
||||
// 第二部分
|
||||
ThemePart{
|
||||
"env": {"00aad4"},
|
||||
"clo": {"fff", "000"},
|
||||
"head": {"ffe0bd"},
|
||||
"mouth": {"ff9a84"},
|
||||
"eyes": {"000", "fff"},
|
||||
"top": {"fff200", "fff200"},
|
||||
},
|
||||
// 第三部分
|
||||
ThemePart{
|
||||
"env": {"00aad4"},
|
||||
"clo": {"fff", "000"},
|
||||
"head": {"ffe0bd"},
|
||||
"mouth": {"ff9a84"},
|
||||
"eyes": {"000", "fff"},
|
||||
"top": {"fff200", "fff200"},
|
||||
},
|
||||
},
|
||||
|
||||
// Guy 主题
|
||||
style.GuyStyle: {
|
||||
ThemePart{
|
||||
"env": {"#6FC30E"},
|
||||
"clo": {"#b4e1fa", "#5b5d6e", "#515262", "#a0d2f0", "#a0d2f0"},
|
||||
"head": {"#fae3b9"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#8eff45", "#8eff45", "none", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#00a58c"},
|
||||
"clo": {"#000", "#5b00", "#5100", "#a000", "#a000"},
|
||||
"head": {"#FAD2B9"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#FFC600", "none", "#FFC600", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#ff501f"},
|
||||
"clo": {"#000", "#ff0000", "#ff0000", "#7d7d7d", "#7d7d7d"},
|
||||
"head": {"#fff3dc"},
|
||||
"mouth": {"#d2001b", "none"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#D2001B", "none", "none", "#D2001B"},
|
||||
},
|
||||
},
|
||||
|
||||
// Country主题
|
||||
style.CountryStyle: {
|
||||
ThemePart{
|
||||
"env": {"#fc0"},
|
||||
"clo": {"#901e0e", "#ffbe1e", "#ffbe1e", "#c55f54"},
|
||||
"head": {"#f8d9ad"},
|
||||
"mouth": {"#000", "none", "#000", "none"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#583D00", "#AF892E", "#462D00", "#a0a0a0"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#386465"},
|
||||
"clo": {"#fff", "#333", "#333", "#333"},
|
||||
"head": {"#FFD79D"},
|
||||
"mouth": {"#000", "#000", "#000", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#27363C", "#5DCAD4", "#314652", "#333"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#DFFF00"},
|
||||
"clo": {"#304267", "#aab0b1", "#aab0b1", "#aab0b1"},
|
||||
"head": {"#e6b876"},
|
||||
"mouth": {"#50230a", "#50230a", "#50230a", "#50230a"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#333", "#afafaf", "#222", "#6d3a1d"},
|
||||
},
|
||||
},
|
||||
|
||||
// Geeknot主题
|
||||
style.GeeknotStyle: {
|
||||
ThemePart{
|
||||
"env": {"#a09300"},
|
||||
"clo": {"#c7d4e2", "#435363", "#435363", "#141720", "#141720", "#e7ecf2", "#e7ecf2"},
|
||||
"head": {"#f5d4a6"},
|
||||
"mouth": {"#000", "#cf9f76"},
|
||||
"eyes": {"#000", "#000", "#000", "#000", "#000", "#000", "#fff", "#fff", "#fff", "#fff", "#000", "#000"},
|
||||
"top": {"none", "#fdff00"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#b3003e"},
|
||||
"clo": {"#000", "#435363", "#435363", "#000", "none", "#e7ecf2", "#e7ecf2"},
|
||||
"head": {"#f5d4a6"},
|
||||
"mouth": {"#000", "#af9f94"},
|
||||
"eyes": {"#9ff3ffdb", "#000", "#9ff3ffdb", "#000", "#2f508a", "#000", "#000", "#000", "none", "none", "none", "none"},
|
||||
"top": {"#ff9a00", "#ff9a00"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#884f00"},
|
||||
"clo": {"#ff0000", "#fff", "#fff", "#141720", "#141720", "#e7ecf2", "#e7ecf2"},
|
||||
"head": {"#c57b14"},
|
||||
"mouth": {"#000", "#cf9f76"},
|
||||
"eyes": {"none", "#000", "none", "#000", "#5a0000", "#000", "#000", "#000", "none", "none", "none", "none"},
|
||||
"top": {"#efefef", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// Asian主题
|
||||
style.AsianStyle: {
|
||||
ThemePart{
|
||||
"env": {"#8acf00"},
|
||||
"clo": {"#ee2829", "#ff0"},
|
||||
"head": {"#ffce73"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#000", "#000", "none", "#000", "#ff4e4e", "#000"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#00d2a3"},
|
||||
"clo": {"#0D0046", "#ffce73"},
|
||||
"head": {"#ffce73"},
|
||||
"mouth": {"#000", "none"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#000", "#000", "#000", "none", "#ffb358", "#000", "none", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#ff184e"},
|
||||
"clo": {"#000", "none"},
|
||||
"head": {"#ffce73"},
|
||||
"mouth": {"#ff0000", "none"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"none", "none", "none", "none", "none", "#ffc107", "none", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// Punk主题
|
||||
style.PunkStyle: {
|
||||
ThemePart{
|
||||
"env": {"#00deae"},
|
||||
"clo": {"#ff0000"},
|
||||
"head": {"#ffce94"},
|
||||
"mouth": {"#f73b6c", "#000"},
|
||||
"eyes": {"#e91e63", "#000", "#e91e63", "#000", "#000", "#000"},
|
||||
"top": {"#dd104f", "#dd104f", "#f73b6c", "#dd104f"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#181284"},
|
||||
"clo": {"#491f49", "#ff9809", "#491f49"},
|
||||
"head": {"#f6ba97"},
|
||||
"mouth": {"#ff9809", "#000"},
|
||||
"eyes": {"#c4ffe4", "#000", "#c4ffe4", "#000", "#000", "#000"},
|
||||
"top": {"none", "none", "#d6f740", "#516303"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#bcf700"},
|
||||
"clo": {"#ff14e4", "#000", "#14fffd"},
|
||||
"head": {"#7b401e"},
|
||||
"mouth": {"#666", "#000"},
|
||||
"eyes": {"#00b5b4", "#000", "#00b5b4", "#000", "#000", "#000"},
|
||||
"top": {"#14fffd", "#14fffd", "#14fffd", "#0d3a62"},
|
||||
},
|
||||
},
|
||||
|
||||
// Afrohair主题
|
||||
style.AfrohairStyle: {
|
||||
ThemePart{
|
||||
"env": {"#0df"},
|
||||
"clo": {"#571e57", "#ff0"},
|
||||
"head": {"#f2c280"},
|
||||
"mouth": {"#ff0000"},
|
||||
"eyes": {"#795548", "#000"},
|
||||
"top": {"#de3b00", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#B400C2"},
|
||||
"clo": {"#0D204A", "#00ffdf"},
|
||||
"head": {"#ca8628"},
|
||||
"mouth": {"#1a1a1a"},
|
||||
"eyes": {"#cbbdaf", "#000"},
|
||||
"top": {"#000", "#000"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#ffe926"},
|
||||
"clo": {"#00d6af", "#000"},
|
||||
"head": {"#8c5100"},
|
||||
"mouth": {"#7d0000"},
|
||||
"eyes": {"none", "#000"},
|
||||
"top": {"#f7f7f7", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// Normie female主题
|
||||
style.NormieFemaleStyle: {
|
||||
ThemePart{
|
||||
"env": {"#4aff0c"},
|
||||
"clo": {"#101010", "#fff", "#fff"},
|
||||
"head": {"#dbbc7f"},
|
||||
"mouth": {"#000"},
|
||||
"eyes": {"#000", "none", "none"},
|
||||
"top": {"#531148", "#531148", "#531148", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#FFC107"},
|
||||
"clo": {"#033c58", "#fff", "#fff"},
|
||||
"head": {"#dbc97f"},
|
||||
"mouth": {"#000"},
|
||||
"eyes": {"none", "#fff", "#000"},
|
||||
"top": {"#FFEB3B", "#FFEB3B", "none", "#FFEB3B"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#FF9800"},
|
||||
"clo": {"#b40000", "#fff", "#fff"},
|
||||
"head": {"#E2AF6B"},
|
||||
"mouth": {"#000"},
|
||||
"eyes": {"none", "#fff", "#000"},
|
||||
"top": {"#ec0000", "#ec0000", "none", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// Older主题
|
||||
style.OlderStyle: {
|
||||
ThemePart{
|
||||
"env": {"#104c8c"},
|
||||
"clo": {"#354B65", "#3D8EBB", "#89D0DA", "#00FFFD"},
|
||||
"head": {"#cc9a5c"},
|
||||
"mouth": {"#222", "#fff"},
|
||||
"eyes": {"#000", "#000"},
|
||||
"top": {"#fff", "#fff", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#0DC15C"},
|
||||
"clo": {"#212121", "#fff", "#212121", "#fff"},
|
||||
"head": {"#dca45f"},
|
||||
"mouth": {"#111", "#633b1d"},
|
||||
"eyes": {"#000", "#000"},
|
||||
"top": {"none", "#792B74", "#792B74"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#ffe500"},
|
||||
"clo": {"#1e5e80", "#fff", "#1e5e80", "#fff"},
|
||||
"head": {"#e8bc86"},
|
||||
"mouth": {"#111", "none"},
|
||||
"eyes": {"#000", "#000"},
|
||||
"top": {"none", "none", "#633b1d"},
|
||||
},
|
||||
},
|
||||
|
||||
// Firehair主题
|
||||
style.FirehairStyle: {
|
||||
ThemePart{
|
||||
"env": {"#4a3f73"},
|
||||
"clo": {"#e6e9ee", "#f1543f", "#ff7058", "#fff", "#fff"},
|
||||
"head": {"#b27e5b"},
|
||||
"mouth": {"#191919", "#191919"},
|
||||
"eyes": {"#000", "#000", "#57FFFD"},
|
||||
"top": {"#ffc", "#ffc", "#ffc"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#00a08d"},
|
||||
"clo": {"#FFBA32", "#484848", "#4e4e4e", "#fff", "#fff"},
|
||||
"head": {"#ab5f2c"},
|
||||
"mouth": {"#191919", "#191919"},
|
||||
"eyes": {"#000", "#ff23fa63", "#000"},
|
||||
"top": {"#ff90f4", "#ff90f4", "#ff90f4"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#22535d"},
|
||||
"clo": {"#000", "#ff2500", "#ff2500", "#fff", "#fff"},
|
||||
"head": {"#a76c44"},
|
||||
"mouth": {"#191919", "#191919"},
|
||||
"eyes": {"#000", "none", "#000"},
|
||||
"top": {"none", "#00efff", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// Blond主题
|
||||
style.BlondStyle: {
|
||||
ThemePart{
|
||||
"env": {"#2668DC"},
|
||||
"clo": {"#2385c6", "#b8d0e0", "#b8d0e0"},
|
||||
"head": {"#ad8a60"},
|
||||
"mouth": {"#000", "#4d4d4d"},
|
||||
"eyes": {"#7fb5a2", "#d1eddf", "#301e19"},
|
||||
"top": {"#fff510", "#fff510"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#643869"},
|
||||
"clo": {"#D67D1B", "#b8d0e0", "#b8d0e0"},
|
||||
"head": {"#CC985A", "none0000"},
|
||||
"mouth": {"#000", "#ececec"},
|
||||
"eyes": {"#1f2644", "#9b97ce", "#301e19"},
|
||||
"top": {"#00eaff", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#F599FF"},
|
||||
"clo": {"#2823C6", "#b8d0e0", "#b8d0e0"},
|
||||
"head": {"#C7873A"},
|
||||
"mouth": {"#000", "#4d4d4d"},
|
||||
"eyes": {"#581b1b", "#FF8B8B", "#000"},
|
||||
"top": {"none", "#9c0092"},
|
||||
},
|
||||
},
|
||||
|
||||
// Ateam主题
|
||||
style.AteamStyle: {
|
||||
ThemePart{
|
||||
"env": {"#d10084"},
|
||||
"clo": {"#efedee", "#00a1e0", "#00a1e0", "#efedee", "#ffce1c"},
|
||||
"head": {"#b35f49"},
|
||||
"mouth": {"#3a484a", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#000", "none", "#000", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#E6C117"},
|
||||
"clo": {"#efedee", "#ec0033", "#ec0033", "#efedee", "#f2ff05"},
|
||||
"head": {"#ffc016"},
|
||||
"mouth": {"#4a3737", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#ffe900", "#ffe900", "none", "#ffe900"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#1d8c00"},
|
||||
"clo": {"#e000cb", "#fff", "#fff", "#e000cb", "#ffce1c"},
|
||||
"head": {"#b96438"},
|
||||
"mouth": {"#000", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#53ffff", "#53ffff", "none", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
// Rasta主题
|
||||
style.RastaStyle: {
|
||||
ThemePart{
|
||||
"env": {"#fc0065"},
|
||||
"clo": {"#708913", "#fdea14", "#708913", "#fdea14", "#708913"},
|
||||
"head": {"#DEA561"},
|
||||
"mouth": {"#444", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f", "#32393f"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#81f72e"},
|
||||
"clo": {"#ff0000", "#ffc107", "#ff0000", "#ffc107", "#ff0000"},
|
||||
"head": {"#ef9831"},
|
||||
"mouth": {"#6b0000", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "#FFFAAD", "none", "none", "none", "none"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#00D872"},
|
||||
"clo": {"#590D00", "#FD1336", "#590D00", "#FD1336", "#590D00"},
|
||||
"head": {"#c36c00"},
|
||||
"mouth": {"#56442b", "#000"},
|
||||
"eyes": {"#000"},
|
||||
"top": {"#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "#004E4C", "none", "none", "none", "none", "none", "none", "none", "none"},
|
||||
},
|
||||
},
|
||||
|
||||
//Meta主题
|
||||
style.MetaStyle: {
|
||||
ThemePart{
|
||||
"env": {"#111"},
|
||||
"clo": {"#000", "#00FFFF"},
|
||||
"head": {"#755227"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"black", "#008a", "aqua"},
|
||||
"top": {"#fff", "#fff", "#fff", "#fff", "#fff"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#00D0D4"},
|
||||
"clo": {"#000", "#fff"},
|
||||
"head": {"#755227"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"black", "#1df7ffa3", "#fcff2c"},
|
||||
"top": {"#fff539", "none", "#fff539", "none", "#fff539"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#DC75FF"},
|
||||
"clo": {"#000", "#FFBDEC"},
|
||||
"head": {"#997549"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"black", "black", "aqua"},
|
||||
"top": {"#00fffd", "none", "none", "none", "none"},
|
||||
},
|
||||
},
|
||||
// Square主题
|
||||
style.SquareStyle: {
|
||||
ThemePart{
|
||||
"env": {"#111"},
|
||||
"clo": {"#000", "#00FFFF"},
|
||||
"head": {"#755227"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"black", "#008a", "aqua"},
|
||||
"top": {"#fff", "#fff", "#fff", "#fff", "#fff"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#00D0D4"},
|
||||
"clo": {"#000", "#fff"},
|
||||
"head": {"#755227"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"black", "#1df7ffa3", "#fcff2c"},
|
||||
"top": {"#fff539", "none", "#fff539", "none", "#fff539"},
|
||||
},
|
||||
ThemePart{
|
||||
"env": {"#DC75FF"},
|
||||
"clo": {"#000", "#FFBDEC"},
|
||||
"head": {"#997549"},
|
||||
"mouth": {"#fff", "#000"},
|
||||
"eyes": {"black", "black", "aqua"},
|
||||
"top": {"#00fffd", "none", "none", "none", "none"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// initThemes 初始化主题数据
|
||||
func (m *Manager) initThemes() {
|
||||
|
||||
for _, theme := range []style.StyleType{
|
||||
style.RoboStyle,
|
||||
style.GirlStyle,
|
||||
style.BlondeStyle,
|
||||
style.GuyStyle,
|
||||
style.CountryStyle,
|
||||
style.GeeknotStyle,
|
||||
style.AsianStyle,
|
||||
style.PunkStyle,
|
||||
style.AfrohairStyle,
|
||||
style.NormieFemaleStyle,
|
||||
style.OlderStyle,
|
||||
style.FirehairStyle,
|
||||
style.BlondStyle,
|
||||
style.AteamStyle,
|
||||
style.RastaStyle,
|
||||
style.MetaStyle,
|
||||
style.SquareStyle,
|
||||
} {
|
||||
if themeSet, exists := defaultThemeSet[theme]; exists {
|
||||
m.AddTheme(themeSet)
|
||||
}
|
||||
}
|
||||
}
|
72
theme/theme.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/landaiqing/go-pixelnebula/errors"
|
||||
)
|
||||
|
||||
// ColorScheme 表示一个颜色方案,包含多个颜色
|
||||
type ColorScheme []string
|
||||
|
||||
// ThemePart 表示主题的一个部分,包含多个组件的颜色方案
|
||||
type ThemePart map[string]ColorScheme
|
||||
|
||||
// Theme 表示一个完整的主题,包含多个部分
|
||||
type Theme []ThemePart
|
||||
|
||||
// Manager 主题管理器,负责管理所有主题
|
||||
type Manager struct {
|
||||
themes []Theme
|
||||
}
|
||||
|
||||
// NewThemeManager 创建一个新的主题管理器
|
||||
func NewThemeManager() *Manager {
|
||||
m := &Manager{}
|
||||
m.initThemes()
|
||||
return m
|
||||
}
|
||||
|
||||
// GetTheme 获取指定索引的主题
|
||||
func (m *Manager) GetTheme(themeIndex, partIndex int) (ThemePart, error) {
|
||||
if themeIndex < 0 || themeIndex >= len(m.themes) {
|
||||
return nil, errors.ErrInvalidTheme
|
||||
}
|
||||
|
||||
theme := m.themes[themeIndex]
|
||||
if partIndex < 0 || partIndex >= len(theme) {
|
||||
return nil, errors.ErrInvalidPart
|
||||
}
|
||||
|
||||
return theme[partIndex], nil
|
||||
}
|
||||
|
||||
// StyleCount 返回主题数量
|
||||
func (m *Manager) StyleCount() int {
|
||||
return len(m.themes)
|
||||
}
|
||||
|
||||
// ThemeCount 返回指定主题的部分数量
|
||||
func (m *Manager) ThemeCount(themeIndex int) int {
|
||||
if themeIndex < 0 || themeIndex >= len(m.themes) {
|
||||
return 0
|
||||
}
|
||||
return len(m.themes[themeIndex])
|
||||
}
|
||||
|
||||
// AddTheme 添加一个新主题
|
||||
func (m *Manager) AddTheme(theme Theme) int {
|
||||
m.themes = append(m.themes, theme)
|
||||
return len(m.themes) - 1
|
||||
}
|
||||
|
||||
// CustomizeTheme 自定义主题
|
||||
func (m *Manager) CustomizeTheme(theme []Theme) {
|
||||
m.themes = theme
|
||||
}
|
||||
|
||||
// GetThemeCountByStyle 获取指定风格索引下的主题数量
|
||||
func (m *Manager) GetThemeCountByStyle(styleIndex int) int {
|
||||
if styleIndex < 0 || styleIndex >= len(m.themes) {
|
||||
return 0
|
||||
}
|
||||
return len(m.themes[styleIndex])
|
||||
}
|