script.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065
  1. // 全局变量
  2. let currentTheme = 'pastel';
  3. let currentStyle = 'cartoon';
  4. let currentCanvasSize = 128;
  5. let currentPixelSize = 8;
  6. let currentSocialMediaFormat = 'custom';
  7. let favorites = JSON.parse(localStorage.getItem('favorites')) || [];
  8. let avatarHistory = JSON.parse(localStorage.getItem('avatarHistory')) || [];
  9. let currentSeed = null;
  10. let useSeed = false;
  11. const maxHistoryItems = 50;
  12. // 社交媒体格式尺寸映射
  13. const socialMediaFormats = {
  14. facebook: 180,
  15. twitter: 400,
  16. instagram: 320,
  17. linkedin: 400,
  18. youtube: 800
  19. };
  20. // 编辑相关变量
  21. let isDrawing = false;
  22. let currentTool = 'brush';
  23. let currentColor = '#000000';
  24. let currentBrushSize = 1;
  25. let history = [];
  26. let historyIndex = -1;
  27. // 颜色主题
  28. const colorThemes = {
  29. pastel: ['#FFB6C1', '#FFD700', '#98FB98', '#87CEEB', '#DDA0DD', '#FFA07A'],
  30. vibrant: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#F7DC6F'],
  31. dark: ['#2C3E50', '#34495E', '#7F8C8D', '#95A5A6', '#BDC3C7', '#ECF0F1'],
  32. monochrome: ['#000000', '#333333', '#666666', '#999999', '#CCCCCC', '#FFFFFF']
  33. };
  34. // 初始化
  35. window.addEventListener('DOMContentLoaded', () => {
  36. initializeApp();
  37. generateAvatar();
  38. loadHistory(); // 加载历史记录
  39. });
  40. function initializeApp() {
  41. // 界面切换
  42. setupNavigation();
  43. // 按钮事件
  44. setupButtons();
  45. // 设置
  46. setupSettings();
  47. // 加载收藏
  48. loadFavorites();
  49. }
  50. // 设置导航
  51. function setupNavigation() {
  52. const navBtns = document.querySelectorAll('.nav-btn');
  53. navBtns.forEach(btn => {
  54. btn.addEventListener('click', () => {
  55. const screen = btn.dataset.screen;
  56. showScreen(screen);
  57. // 更新导航按钮状态
  58. navBtns.forEach(b => b.classList.remove('active'));
  59. btn.classList.add('active');
  60. });
  61. });
  62. // 返回按钮
  63. document.getElementById('backToGenerate').addEventListener('click', () => {
  64. showScreen('generate-screen');
  65. document.getElementById('nav-generate').classList.add('active');
  66. document.getElementById('nav-favorites').classList.remove('active');
  67. });
  68. document.getElementById('backToGenerate2').addEventListener('click', () => {
  69. showScreen('generate-screen');
  70. document.getElementById('nav-generate').classList.add('active');
  71. document.getElementById('nav-settings').classList.remove('active');
  72. });
  73. document.getElementById('backToGenerate3').addEventListener('click', () => {
  74. showScreen('generate-screen');
  75. document.getElementById('nav-generate').classList.add('active');
  76. });
  77. document.getElementById('backToGenerate4').addEventListener('click', () => {
  78. showScreen('generate-screen');
  79. document.getElementById('nav-generate').classList.add('active');
  80. document.getElementById('nav-history').classList.remove('active');
  81. });
  82. }
  83. // 显示指定界面
  84. function showScreen(screenId) {
  85. const screens = document.querySelectorAll('.screen');
  86. screens.forEach(screen => screen.classList.remove('active'));
  87. document.getElementById(screenId).classList.add('active');
  88. }
  89. // 设置按钮事件
  90. function setupButtons() {
  91. // 重新生成按钮
  92. document.getElementById('regenerateBtn').addEventListener('click', generateAvatar);
  93. // 保存按钮
  94. document.getElementById('saveBtn').addEventListener('click', saveAvatar);
  95. // 编辑按钮
  96. document.getElementById('editBtn').addEventListener('click', openEditScreen);
  97. // 收藏按钮
  98. document.getElementById('favoriteBtn').addEventListener('click', addToFavorites);
  99. // 下载按钮
  100. document.getElementById('downloadBtn').addEventListener('click', downloadAvatar);
  101. // 主题按钮
  102. const themeBtns = document.querySelectorAll('.theme-btn');
  103. themeBtns.forEach(btn => {
  104. btn.addEventListener('click', () => {
  105. currentTheme = btn.dataset.theme;
  106. themeBtns.forEach(b => b.classList.remove('active'));
  107. btn.classList.add('active');
  108. generateAvatar();
  109. });
  110. });
  111. // 风格按钮
  112. const styleBtns = document.querySelectorAll('.style-btn');
  113. styleBtns.forEach(btn => {
  114. btn.addEventListener('click', () => {
  115. currentStyle = btn.dataset.style;
  116. styleBtns.forEach(b => b.classList.remove('active'));
  117. btn.classList.add('active');
  118. generateAvatar();
  119. });
  120. });
  121. // 细节按钮
  122. const detailBtns = document.querySelectorAll('.detail-btn');
  123. detailBtns.forEach(btn => {
  124. btn.addEventListener('click', () => {
  125. // 随机化特定细节
  126. generateAvatarWithFocus(btn.dataset.detail);
  127. });
  128. });
  129. // 社交媒体格式按钮
  130. const socialBtns = document.querySelectorAll('.social-options .theme-btn');
  131. socialBtns.forEach(btn => {
  132. btn.addEventListener('click', () => {
  133. const format = btn.dataset.format;
  134. setSocialMediaFormat(format);
  135. });
  136. });
  137. // 种子相关按钮
  138. setupSeedButtons();
  139. // 编辑界面按钮
  140. setupEditButtons();
  141. // 历史记录按钮
  142. const clearHistoryBtn = document.getElementById('clearHistoryBtn');
  143. if (clearHistoryBtn) {
  144. clearHistoryBtn.addEventListener('click', clearHistory);
  145. }
  146. }
  147. // 设置设置选项
  148. function setupSettings() {
  149. // 画布大小
  150. const canvasSizeSelect = document.getElementById('canvasSize');
  151. canvasSizeSelect.addEventListener('change', (e) => {
  152. currentCanvasSize = parseInt(e.target.value);
  153. currentSocialMediaFormat = 'custom';
  154. updateCanvasSize();
  155. generateAvatar();
  156. });
  157. // 社交媒体格式
  158. const socialMediaFormatSelect = document.getElementById('socialMediaFormat');
  159. socialMediaFormatSelect.addEventListener('change', (e) => {
  160. currentSocialMediaFormat = e.target.value;
  161. if (currentSocialMediaFormat !== 'custom') {
  162. // 设置为对应社交媒体格式的尺寸
  163. currentCanvasSize = socialMediaFormats[currentSocialMediaFormat];
  164. canvasSizeSelect.value = currentCanvasSize;
  165. updateCanvasSize();
  166. generateAvatar();
  167. }
  168. });
  169. // 像素大小
  170. const pixelSizeSelect = document.getElementById('pixelSize');
  171. pixelSizeSelect.addEventListener('change', (e) => {
  172. currentPixelSize = parseInt(e.target.value);
  173. generateAvatar();
  174. });
  175. // 界面主题
  176. const appThemeSelect = document.getElementById('appTheme');
  177. appThemeSelect.addEventListener('change', (e) => {
  178. const theme = e.target.value;
  179. document.body.className = theme === 'dark' ? 'dark-theme' : '';
  180. localStorage.setItem('appTheme', theme);
  181. });
  182. // 加载保存的主题
  183. const savedTheme = localStorage.getItem('appTheme') || 'light';
  184. appThemeSelect.value = savedTheme;
  185. document.body.className = savedTheme === 'dark' ? 'dark-theme' : '';
  186. // 清空收藏
  187. document.getElementById('clearFavorites').addEventListener('click', () => {
  188. if (confirm('确定要清空所有收藏吗?')) {
  189. favorites = [];
  190. localStorage.setItem('favorites', JSON.stringify(favorites));
  191. loadFavorites();
  192. }
  193. });
  194. }
  195. // 设置编辑界面按钮
  196. function setupEditButtons() {
  197. // 返回按钮
  198. document.getElementById('backToGenerate3').addEventListener('click', () => {
  199. showScreen('generate-screen');
  200. document.getElementById('nav-generate').classList.add('active');
  201. });
  202. // 颜色选择器
  203. document.getElementById('colorPicker').addEventListener('input', (e) => {
  204. currentColor = e.target.value;
  205. });
  206. // 画笔大小
  207. document.getElementById('brushSize').addEventListener('change', (e) => {
  208. currentBrushSize = parseInt(e.target.value);
  209. });
  210. // 工具按钮
  211. const toolBtns = document.querySelectorAll('.tool-btn');
  212. toolBtns.forEach(btn => {
  213. btn.addEventListener('click', () => {
  214. currentTool = btn.dataset.tool;
  215. toolBtns.forEach(b => b.classList.remove('active'));
  216. btn.classList.add('active');
  217. });
  218. });
  219. // 撤销/重做按钮
  220. document.getElementById('undoBtn').addEventListener('click', undo);
  221. document.getElementById('redoBtn').addEventListener('click', redo);
  222. // 保存编辑
  223. document.getElementById('saveEditBtn').addEventListener('click', saveEdit);
  224. }
  225. // 打开编辑界面
  226. function openEditScreen() {
  227. const avatarCanvas = document.getElementById('avatarCanvas');
  228. const editCanvas = document.getElementById('editCanvas');
  229. const ctx = editCanvas.getContext('2d');
  230. // 设置编辑画布大小与生成画布一致
  231. editCanvas.width = avatarCanvas.width;
  232. editCanvas.height = avatarCanvas.height;
  233. // 复制当前头像到编辑画布
  234. ctx.drawImage(avatarCanvas, 0, 0);
  235. // 初始化编辑工具
  236. currentTool = 'brush';
  237. currentColor = '#000000';
  238. currentBrushSize = 1;
  239. // 重置历史记录
  240. history = [];
  241. historyIndex = -1;
  242. saveToHistory();
  243. // 设置画布事件监听
  244. setupCanvasEventListeners();
  245. // 显示编辑界面
  246. showScreen('edit-screen');
  247. }
  248. // 设置画布事件监听
  249. function setupCanvasEventListeners() {
  250. const canvas = document.getElementById('editCanvas');
  251. // 移除旧的事件监听
  252. canvas.removeEventListener('mousedown', startDrawing);
  253. canvas.removeEventListener('mousemove', draw);
  254. canvas.removeEventListener('mouseup', stopDrawing);
  255. canvas.removeEventListener('mouseout', stopDrawing);
  256. canvas.removeEventListener('touchstart', startDrawing);
  257. canvas.removeEventListener('touchmove', draw);
  258. canvas.removeEventListener('touchend', stopDrawing);
  259. // 添加新的事件监听
  260. canvas.addEventListener('mousedown', startDrawing);
  261. canvas.addEventListener('mousemove', draw);
  262. canvas.addEventListener('mouseup', stopDrawing);
  263. canvas.addEventListener('mouseout', stopDrawing);
  264. canvas.addEventListener('touchstart', startDrawing);
  265. canvas.addEventListener('touchmove', draw);
  266. canvas.addEventListener('touchend', stopDrawing);
  267. }
  268. // 开始绘制
  269. function startDrawing(e) {
  270. isDrawing = true;
  271. draw(e);
  272. }
  273. // 绘制
  274. function draw(e) {
  275. if (!isDrawing) return;
  276. const canvas = document.getElementById('editCanvas');
  277. const ctx = canvas.getContext('2d');
  278. // 获取鼠标/触摸位置
  279. let rect = canvas.getBoundingClientRect();
  280. let x, y;
  281. if (e.type.startsWith('mouse')) {
  282. x = e.clientX - rect.left;
  283. y = e.clientY - rect.top;
  284. } else {
  285. e.preventDefault();
  286. x = e.touches[0].clientX - rect.left;
  287. y = e.touches[0].clientY - rect.top;
  288. }
  289. // 转换为像素坐标(对齐到像素网格)
  290. x = Math.floor(x / currentPixelSize) * currentPixelSize;
  291. y = Math.floor(y / currentPixelSize) * currentPixelSize;
  292. // 根据工具执行不同操作
  293. switch (currentTool) {
  294. case 'brush':
  295. drawBrush(ctx, x, y);
  296. break;
  297. case 'eraser':
  298. drawEraser(ctx, x, y);
  299. break;
  300. case 'fill':
  301. fillBucket(ctx, x, y);
  302. isDrawing = false;
  303. break;
  304. }
  305. }
  306. // 停止绘制
  307. function stopDrawing() {
  308. if (isDrawing) {
  309. isDrawing = false;
  310. saveToHistory();
  311. }
  312. }
  313. // 画笔绘制
  314. function drawBrush(ctx, x, y) {
  315. ctx.fillStyle = currentColor;
  316. ctx.fillRect(x, y, currentBrushSize * currentPixelSize, currentBrushSize * currentPixelSize);
  317. }
  318. // 橡皮擦绘制
  319. function drawEraser(ctx, x, y) {
  320. ctx.clearRect(x, y, currentBrushSize * currentPixelSize, currentBrushSize * currentPixelSize);
  321. }
  322. // 填充工具
  323. function fillBucket(ctx, x, y) {
  324. const canvas = ctx.canvas;
  325. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  326. const data = imageData.data;
  327. const targetColor = getPixelColor(data, x, y, canvas.width);
  328. const fillColor = hexToRgb(currentColor);
  329. if (colorsMatch(targetColor, fillColor)) return;
  330. const stack = [{x, y}];
  331. while (stack.length > 0) {
  332. const pixel = stack.pop();
  333. const currentPixelColor = getPixelColor(data, pixel.x, pixel.y, canvas.width);
  334. if (colorsMatch(currentPixelColor, targetColor)) {
  335. setPixelColor(data, pixel.x, pixel.y, fillColor, canvas.width);
  336. // 检查相邻像素
  337. if (pixel.x > 0) stack.push({x: pixel.x - currentPixelSize, y: pixel.y});
  338. if (pixel.x < canvas.width - currentPixelSize) stack.push({x: pixel.x + currentPixelSize, y: pixel.y});
  339. if (pixel.y > 0) stack.push({x: pixel.x, y: pixel.y - currentPixelSize});
  340. if (pixel.y < canvas.height - currentPixelSize) stack.push({x: pixel.x, y: pixel.y + currentPixelSize});
  341. }
  342. }
  343. ctx.putImageData(imageData, 0, 0);
  344. }
  345. // 获取像素颜色
  346. function getPixelColor(data, x, y, width) {
  347. const index = (y * width + x) * 4;
  348. return {
  349. r: data[index],
  350. g: data[index + 1],
  351. b: data[index + 2],
  352. a: data[index + 3]
  353. };
  354. }
  355. // 设置像素颜色
  356. function setPixelColor(data, x, y, color, width) {
  357. const index = (y * width + x) * 4;
  358. data[index] = color.r;
  359. data[index + 1] = color.g;
  360. data[index + 2] = color.b;
  361. data[index + 3] = 255; // 不透明
  362. }
  363. // 颜色匹配
  364. function colorsMatch(color1, color2) {
  365. return color1.r === color2.r && color1.g === color2.g && color1.b === color2.b;
  366. }
  367. // 十六进制转RGB
  368. function hexToRgb(hex) {
  369. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  370. return result ? {
  371. r: parseInt(result[1], 16),
  372. g: parseInt(result[2], 16),
  373. b: parseInt(result[3], 16)
  374. } : null;
  375. }
  376. // 保存到历史记录
  377. function saveToHistory() {
  378. const canvas = document.getElementById('editCanvas');
  379. const dataURL = canvas.toDataURL();
  380. // 移除当前索引之后的历史记录
  381. if (historyIndex < history.length - 1) {
  382. history = history.slice(0, historyIndex + 1);
  383. }
  384. history.push(dataURL);
  385. historyIndex = history.length - 1;
  386. }
  387. // 撤销
  388. function undo() {
  389. if (historyIndex > 0) {
  390. historyIndex--;
  391. loadFromHistory();
  392. }
  393. }
  394. // 重做
  395. function redo() {
  396. if (historyIndex < history.length - 1) {
  397. historyIndex++;
  398. loadFromHistory();
  399. }
  400. }
  401. // 从历史记录加载
  402. function loadFromHistory() {
  403. const canvas = document.getElementById('editCanvas');
  404. const ctx = canvas.getContext('2d');
  405. const img = new Image();
  406. img.onload = function() {
  407. ctx.clearRect(0, 0, canvas.width, canvas.height);
  408. ctx.drawImage(img, 0, 0);
  409. };
  410. img.src = history[historyIndex];
  411. }
  412. // 保存编辑
  413. function saveEdit() {
  414. const editCanvas = document.getElementById('editCanvas');
  415. const avatarCanvas = document.getElementById('avatarCanvas');
  416. const ctx = avatarCanvas.getContext('2d');
  417. // 设置生成画布大小与编辑画布一致
  418. avatarCanvas.width = editCanvas.width;
  419. avatarCanvas.height = editCanvas.height;
  420. // 复制编辑后的头像回生成画布
  421. ctx.drawImage(editCanvas, 0, 0);
  422. // 返回生成界面
  423. showScreen('generate-screen');
  424. document.getElementById('nav-generate').classList.add('active');
  425. alert('编辑已保存!');
  426. }
  427. // 设置种子相关按钮
  428. function setupSeedButtons() {
  429. const seedInput = document.getElementById('seedInput');
  430. const copySeedBtn = document.getElementById('copySeedBtn');
  431. const randomSeedBtn = document.getElementById('randomSeedBtn');
  432. // 种子输入事件
  433. seedInput.addEventListener('input', (e) => {
  434. setSeed(e.target.value);
  435. });
  436. // 复制种子按钮
  437. copySeedBtn.addEventListener('click', () => {
  438. const seedText = seedInput.value;
  439. if (seedText) {
  440. navigator.clipboard.writeText(seedText).then(() => {
  441. alert('种子已复制到剪贴板!');
  442. }).catch(err => {
  443. console.error('复制失败:', err);
  444. alert('复制失败,请手动复制');
  445. });
  446. } else {
  447. alert('没有可复制的种子');
  448. }
  449. });
  450. // 生成随机种子按钮
  451. randomSeedBtn.addEventListener('click', () => {
  452. const randomSeed = generateRandomSeed();
  453. seedInput.value = randomSeed;
  454. setSeed(randomSeed);
  455. generateAvatar();
  456. });
  457. // 当重新生成头像时,如果没有种子则生成一个
  458. document.getElementById('regenerateBtn').addEventListener('click', () => {
  459. if (!seedInput.value.trim()) {
  460. const randomSeed = generateRandomSeed();
  461. seedInput.value = randomSeed;
  462. setSeed(randomSeed);
  463. }
  464. });
  465. }
  466. // 设置社交媒体格式
  467. function setSocialMediaFormat(format) {
  468. currentSocialMediaFormat = format;
  469. // 设置为对应社交媒体格式的尺寸
  470. currentCanvasSize = socialMediaFormats[currentSocialMediaFormat];
  471. // 更新设置界面中的选择器
  472. const canvasSizeSelect = document.getElementById('canvasSize');
  473. const socialMediaFormatSelect = document.getElementById('socialMediaFormat');
  474. canvasSizeSelect.value = currentCanvasSize;
  475. socialMediaFormatSelect.value = currentSocialMediaFormat;
  476. // 更新画布大小
  477. updateCanvasSize();
  478. // 重新生成头像
  479. generateAvatar();
  480. // 更新社交媒体格式按钮的激活状态
  481. const socialBtns = document.querySelectorAll('.social-options .theme-btn');
  482. socialBtns.forEach(btn => {
  483. btn.classList.remove('active');
  484. if (btn.dataset.format === format) {
  485. btn.classList.add('active');
  486. }
  487. });
  488. }
  489. // 更新画布大小
  490. function updateCanvasSize() {
  491. const canvas = document.getElementById('avatarCanvas');
  492. canvas.width = currentCanvasSize;
  493. canvas.height = currentCanvasSize;
  494. }
  495. // 生成头像
  496. function generateAvatar() {
  497. const canvas = document.getElementById('avatarCanvas');
  498. const ctx = canvas.getContext('2d');
  499. // 如果有种子输入但未设置,先设置种子
  500. const seedInput = document.getElementById('seedInput');
  501. if (seedInput && seedInput.value && seedInput.value.trim() !== '') {
  502. setSeed(seedInput.value);
  503. }
  504. // 清空画布
  505. ctx.clearRect(0, 0, canvas.width, canvas.height);
  506. // 绘制背景
  507. ctx.fillStyle = getRandomColor();
  508. ctx.fillRect(0, 0, canvas.width, canvas.height);
  509. // 根据风格生成头像
  510. switch(currentStyle) {
  511. case 'cartoon':
  512. drawCartoonStyle(ctx);
  513. break;
  514. case 'pixelart':
  515. drawPixelArtStyle(ctx);
  516. break;
  517. case 'minimal':
  518. drawMinimalStyle(ctx);
  519. break;
  520. }
  521. // 保存到历史记录
  522. saveAvatarToHistory();
  523. }
  524. // 生成特定细节的头像
  525. function generateAvatarWithFocus(focus) {
  526. const canvas = document.getElementById('avatarCanvas');
  527. const ctx = canvas.getContext('2d');
  528. // 保存当前头像
  529. const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  530. // 重新生成
  531. generateAvatar();
  532. // 根据焦点重新绘制特定部分
  533. switch(focus) {
  534. case 'eyes':
  535. drawEyes(ctx);
  536. break;
  537. case 'nose':
  538. drawNose(ctx);
  539. break;
  540. case 'mouth':
  541. drawMouth(ctx);
  542. break;
  543. case 'accessories':
  544. drawAccessories(ctx);
  545. break;
  546. }
  547. }
  548. // 基于种子的随机数生成器
  549. function seededRandom(seed) {
  550. let x = Math.sin(seed) * 10000;
  551. return x - Math.floor(x);
  552. }
  553. // 获取当前随机数生成器
  554. function getRandom() {
  555. if (useSeed && currentSeed !== null) {
  556. currentSeed = (currentSeed + 0.123456789) % 1;
  557. return seededRandom(currentSeed);
  558. }
  559. return Math.random();
  560. }
  561. // 设置随机种子
  562. function setSeed(seed) {
  563. if (seed && seed.trim() !== '') {
  564. // 将字符串种子转换为数字
  565. let numSeed = 0;
  566. for (let i = 0; i < seed.length; i++) {
  567. numSeed += seed.charCodeAt(i) * Math.pow(31, i);
  568. }
  569. currentSeed = numSeed % 1;
  570. useSeed = true;
  571. } else {
  572. useSeed = false;
  573. currentSeed = null;
  574. }
  575. }
  576. // 生成随机种子字符串
  577. function generateRandomSeed() {
  578. const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  579. let result = '';
  580. for (let i = 0; i < 10; i++) {
  581. result += chars.charAt(Math.floor(Math.random() * chars.length));
  582. }
  583. return result;
  584. }
  585. // 获取随机颜色
  586. function getRandomColor() {
  587. const colors = colorThemes[currentTheme];
  588. return colors[Math.floor(getRandom() * colors.length)];
  589. }
  590. // 卡通风格
  591. function drawCartoonStyle(ctx) {
  592. const centerX = ctx.canvas.width / 2;
  593. const centerY = ctx.canvas.height / 2;
  594. const size = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.8;
  595. // 头部
  596. ctx.fillStyle = getRandomColor();
  597. ctx.beginPath();
  598. ctx.arc(centerX, centerY, size / 2, 0, Math.PI * 2);
  599. ctx.fill();
  600. drawEyes(ctx);
  601. drawNose(ctx);
  602. drawMouth(ctx);
  603. drawAccessories(ctx);
  604. }
  605. // 像素艺术风格
  606. function drawPixelArtStyle(ctx) {
  607. const gridSize = currentPixelSize;
  608. const cols = Math.floor(ctx.canvas.width / gridSize);
  609. const rows = Math.floor(ctx.canvas.height / gridSize);
  610. // 头部轮廓
  611. for (let y = 2; y < rows - 2; y++) {
  612. for (let x = 2; x < cols - 2; x++) {
  613. if ((x === 2 || x === cols - 3) && (y > 4 && y < rows - 5)) continue;
  614. if (Math.abs(x - cols/2) + Math.abs(y - rows/2) < rows/2 - 1) {
  615. ctx.fillStyle = getRandomColor();
  616. ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
  617. }
  618. }
  619. }
  620. // 眼睛
  621. drawPixelEyes(ctx, gridSize, cols, rows);
  622. // 嘴巴
  623. drawPixelMouth(ctx, gridSize, cols, rows);
  624. }
  625. // 简约风格
  626. function drawMinimalStyle(ctx) {
  627. const centerX = ctx.canvas.width / 2;
  628. const centerY = ctx.canvas.height / 2;
  629. const size = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.6;
  630. // 头部
  631. ctx.fillStyle = getRandomColor();
  632. ctx.fillRect(centerX - size/2, centerY - size/2, size, size);
  633. // 眼睛
  634. const eyeSize = size * 0.1;
  635. ctx.fillStyle = '#000000';
  636. ctx.fillRect(centerX - size/4 - eyeSize/2, centerY - size/6, eyeSize, eyeSize);
  637. ctx.fillRect(centerX + size/4 - eyeSize/2, centerY - size/6, eyeSize, eyeSize);
  638. // 嘴巴
  639. ctx.fillRect(centerX - size/6, centerY + size/6, size/3, eyeSize/2);
  640. }
  641. // 绘制眼睛
  642. function drawEyes(ctx) {
  643. const centerX = ctx.canvas.width / 2;
  644. const centerY = ctx.canvas.height / 2;
  645. const eyeSize = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.15;
  646. ctx.fillStyle = '#FFFFFF';
  647. ctx.beginPath();
  648. ctx.arc(centerX - eyeSize * 1.5, centerY - eyeSize/2, eyeSize, 0, Math.PI * 2);
  649. ctx.arc(centerX + eyeSize * 1.5, centerY - eyeSize/2, eyeSize, 0, Math.PI * 2);
  650. ctx.fill();
  651. ctx.fillStyle = '#000000';
  652. ctx.beginPath();
  653. ctx.arc(centerX - eyeSize * 1.5, centerY - eyeSize/2, eyeSize * 0.4, 0, Math.PI * 2);
  654. ctx.arc(centerX + eyeSize * 1.5, centerY - eyeSize/2, eyeSize * 0.4, 0, Math.PI * 2);
  655. ctx.fill();
  656. }
  657. // 绘制鼻子
  658. function drawNose(ctx) {
  659. const centerX = ctx.canvas.width / 2;
  660. const centerY = ctx.canvas.height / 2;
  661. const noseSize = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.08;
  662. ctx.fillStyle = getRandomColor();
  663. ctx.beginPath();
  664. ctx.arc(centerX, centerY, noseSize, 0, Math.PI * 2);
  665. ctx.fill();
  666. }
  667. // 绘制嘴巴
  668. function drawMouth(ctx) {
  669. const centerX = ctx.canvas.width / 2;
  670. const centerY = ctx.canvas.height / 2;
  671. const mouthSize = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.25;
  672. ctx.fillStyle = '#FF0000';
  673. ctx.beginPath();
  674. ctx.arc(centerX, centerY + mouthSize/3, mouthSize/2, 0, Math.PI);
  675. ctx.fill();
  676. }
  677. // 绘制装饰
  678. function drawAccessories(ctx) {
  679. const centerX = ctx.canvas.width / 2;
  680. const centerY = ctx.canvas.height / 2;
  681. const size = Math.min(ctx.canvas.width, ctx.canvas.height) * 0.8;
  682. // 随机添加装饰
  683. const accessoryType = Math.floor(getRandom() * 3);
  684. switch(accessoryType) {
  685. case 0: // 帽子
  686. ctx.fillStyle = getRandomColor();
  687. ctx.fillRect(centerX - size/3, centerY - size/2 - size/6, size * 2/3, size/6);
  688. ctx.fillRect(centerX - size/2.5, centerY - size/2, size * 2/5, size/10);
  689. break;
  690. case 1: // 眼镜
  691. ctx.strokeStyle = getRandomColor();
  692. ctx.lineWidth = 3;
  693. ctx.beginPath();
  694. ctx.arc(centerX - size/4, centerY - size/6, size/8, 0, Math.PI * 2);
  695. ctx.arc(centerX + size/4, centerY - size/6, size/8, 0, Math.PI * 2);
  696. ctx.stroke();
  697. ctx.beginPath();
  698. ctx.moveTo(centerX - size/8, centerY - size/6);
  699. ctx.lineTo(centerX + size/8, centerY - size/6);
  700. ctx.stroke();
  701. break;
  702. case 2: // 耳朵
  703. ctx.fillStyle = getRandomColor();
  704. ctx.beginPath();
  705. ctx.arc(centerX - size/2, centerY, size/6, 0, Math.PI * 2);
  706. ctx.arc(centerX + size/2, centerY, size/6, 0, Math.PI * 2);
  707. ctx.fill();
  708. break;
  709. }
  710. }
  711. // 绘制像素风格眼睛
  712. function drawPixelEyes(ctx, gridSize, cols, rows) {
  713. const centerX = cols / 2;
  714. const centerY = rows / 2;
  715. ctx.fillStyle = '#FFFFFF';
  716. // 左眼
  717. for (let y = centerY - 2; y <= centerY + 1; y++) {
  718. for (let x = centerX - 4; x <= centerX - 2; x++) {
  719. ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
  720. }
  721. }
  722. // 右眼
  723. for (let y = centerY - 2; y <= centerY + 1; y++) {
  724. for (let x = centerX + 2; x <= centerX + 4; x++) {
  725. ctx.fillRect(x * gridSize, y * gridSize, gridSize, gridSize);
  726. }
  727. }
  728. ctx.fillStyle = '#000000';
  729. // 左瞳孔
  730. ctx.fillRect((centerX - 3) * gridSize, centerY * gridSize, gridSize, gridSize);
  731. // 右瞳孔
  732. ctx.fillRect((centerX + 3) * gridSize, centerY * gridSize, gridSize, gridSize);
  733. }
  734. // 绘制像素风格嘴巴
  735. function drawPixelMouth(ctx, gridSize, cols, rows) {
  736. const centerX = cols / 2;
  737. const centerY = rows / 2;
  738. ctx.fillStyle = '#FF0000';
  739. for (let x = centerX - 3; x <= centerX + 3; x++) {
  740. ctx.fillRect(x * gridSize, (centerY + 3) * gridSize, gridSize, gridSize);
  741. }
  742. for (let x = centerX - 2; x <= centerX + 2; x++) {
  743. ctx.fillRect(x * gridSize, (centerY + 4) * gridSize, gridSize, gridSize);
  744. }
  745. }
  746. // 保存头像
  747. function saveAvatar() {
  748. const canvas = document.getElementById('avatarCanvas');
  749. const dataURL = canvas.toDataURL('image/png');
  750. // 如果自动保存开启,则保存到本地
  751. if (document.getElementById('autoSave').checked) {
  752. localStorage.setItem('lastAvatar', dataURL);
  753. alert('头像已保存!');
  754. }
  755. }
  756. // 添加到收藏
  757. function addToFavorites() {
  758. const canvas = document.getElementById('avatarCanvas');
  759. const dataURL = canvas.toDataURL('image/png');
  760. // 检查是否已存在
  761. const exists = favorites.some(fav => fav.dataURL === dataURL);
  762. if (exists) {
  763. alert('该头像已在收藏中!');
  764. return;
  765. }
  766. favorites.push({
  767. id: Date.now(),
  768. dataURL: dataURL,
  769. timestamp: new Date().toISOString()
  770. });
  771. localStorage.setItem('favorites', JSON.stringify(favorites));
  772. loadFavorites();
  773. alert('头像已添加到收藏!');
  774. }
  775. // 下载头像
  776. function downloadAvatar() {
  777. const canvas = document.getElementById('avatarCanvas');
  778. const link = document.createElement('a');
  779. link.download = `pixel-avatar-${Date.now()}.png`;
  780. link.href = canvas.toDataURL('image/png');
  781. link.click();
  782. }
  783. // 加载收藏
  784. function loadFavorites() {
  785. const grid = document.getElementById('favorites-grid');
  786. grid.innerHTML = '';
  787. if (favorites.length === 0) {
  788. grid.innerHTML = '<p style="text-align: center; padding: 20px;">暂无收藏</p>';
  789. return;
  790. }
  791. favorites.forEach(fav => {
  792. const item = document.createElement('div');
  793. item.className = 'favorite-item';
  794. const img = document.createElement('img');
  795. img.src = fav.dataURL;
  796. img.alt = '收藏的头像';
  797. const removeBtn = document.createElement('button');
  798. removeBtn.className = 'remove-favorite';
  799. removeBtn.textContent = '×';
  800. removeBtn.addEventListener('click', () => removeFavorite(fav.id));
  801. item.appendChild(img);
  802. item.appendChild(removeBtn);
  803. grid.appendChild(item);
  804. });
  805. }
  806. // 移除收藏
  807. function removeFavorite(id) {
  808. favorites = favorites.filter(fav => fav.id !== id);
  809. localStorage.setItem('favorites', JSON.stringify(favorites));
  810. loadFavorites();
  811. }
  812. // 自动保存
  813. window.addEventListener('beforeunload', () => {
  814. if (document.getElementById('autoSave').checked) {
  815. saveAvatar();
  816. }
  817. });
  818. // 保存头像到历史记录
  819. function saveAvatarToHistory() {
  820. const canvas = document.getElementById('avatarCanvas');
  821. const dataURL = canvas.toDataURL('image/png');
  822. const seedInput = document.getElementById('seedInput');
  823. const seed = seedInput.value || 'random';
  824. // 检查是否与最近的历史记录相同
  825. if (avatarHistory.length > 0 && avatarHistory[0].dataURL === dataURL) {
  826. return;
  827. }
  828. // 添加新记录到历史
  829. avatarHistory.unshift({
  830. id: Date.now(),
  831. dataURL: dataURL,
  832. seed: seed,
  833. timestamp: new Date().toISOString()
  834. });
  835. // 限制历史记录数量
  836. if (avatarHistory.length > maxHistoryItems) {
  837. avatarHistory = avatarHistory.slice(0, maxHistoryItems);
  838. }
  839. localStorage.setItem('avatarHistory', JSON.stringify(avatarHistory));
  840. // 如果当前在历史记录界面,更新显示
  841. if (document.getElementById('history-screen').classList.contains('active')) {
  842. loadHistory();
  843. }
  844. }
  845. // 加载历史记录
  846. function loadHistory() {
  847. const grid = document.getElementById('history-grid');
  848. grid.innerHTML = '';
  849. if (avatarHistory.length === 0) {
  850. grid.innerHTML = '<p style="text-align: center; padding: 20px;">暂无历史记录</p>';
  851. return;
  852. }
  853. avatarHistory.forEach(item => {
  854. const historyItem = document.createElement('div');
  855. historyItem.className = 'favorite-item';
  856. const img = document.createElement('img');
  857. img.src = item.dataURL;
  858. img.alt = `历史头像 ${new Date(item.timestamp).toLocaleString()}`;
  859. // 添加点击事件,可查看详细信息
  860. img.addEventListener('click', () => {
  861. // 将历史记录中的头像恢复到生成界面
  862. const avatarCanvas = document.getElementById('avatarCanvas');
  863. const ctx = avatarCanvas.getContext('2d');
  864. const historyImg = new Image();
  865. historyImg.onload = function() {
  866. avatarCanvas.width = historyImg.width;
  867. avatarCanvas.height = historyImg.height;
  868. ctx.drawImage(historyImg, 0, 0);
  869. // 设置种子
  870. const seedInput = document.getElementById('seedInput');
  871. seedInput.value = item.seed;
  872. if (item.seed !== 'random') {
  873. setSeed(item.seed);
  874. }
  875. // 切换到生成界面
  876. showScreen('generate-screen');
  877. document.getElementById('nav-generate').classList.add('active');
  878. document.getElementById('nav-history').classList.remove('active');
  879. };
  880. historyImg.src = item.dataURL;
  881. });
  882. historyItem.appendChild(img);
  883. grid.appendChild(historyItem);
  884. });
  885. }
  886. // 清空历史记录
  887. function clearHistory() {
  888. if (confirm('确定要清空所有历史记录吗?')) {
  889. avatarHistory = [];
  890. localStorage.removeItem('avatarHistory');
  891. loadHistory();
  892. alert('历史记录已清空!');
  893. }
  894. }