index.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. const fs = require('fs');
  2. const path = require('path');
  3. const sharp = require('sharp');
  4. // 配置
  5. const CONFIG = {
  6. pixelSize: 100, // 马赛克块大小
  7. inputImage: path.join(__dirname, '../参考图.png'),
  8. outputMosaic: path.join(__dirname, '../mosaic.png'),
  9. outputReport: path.join(__dirname, '../report.html')
  10. };
  11. // 主函数
  12. async function main() {
  13. try {
  14. console.log('开始处理图片...');
  15. // 获取图片信息
  16. const metadata = await sharp(CONFIG.inputImage).metadata();
  17. const { width, height } = metadata;
  18. // 创建马赛克图片
  19. console.log('创建马赛克图片...');
  20. const mosaicBuffer = await createMosaic(CONFIG.inputImage, CONFIG.pixelSize);
  21. await fs.promises.writeFile(CONFIG.outputMosaic, mosaicBuffer);
  22. // 提取颜色
  23. console.log('提取颜色信息...');
  24. const colorData = await extractColors(mosaicBuffer, CONFIG.pixelSize, width, height);
  25. // 生成报告
  26. console.log('生成HTML报告...');
  27. await generateReport(colorData);
  28. console.log('处理完成! 报告已生成: ' + CONFIG.outputReport);
  29. } catch (error) {
  30. console.error('处理过程中出错:', error);
  31. }
  32. }
  33. // 创建马赛克图片
  34. async function createMosaic(inputPath, pixelSize) {
  35. // 获取图片信息
  36. const metadata = await sharp(inputPath).metadata();
  37. const { width, height } = metadata;
  38. // 计算缩小后的尺寸
  39. const smallWidth = Math.ceil(width / pixelSize);
  40. const smallHeight = Math.ceil(height / pixelSize);
  41. // 先缩小图片
  42. const smallBuffer = await sharp(inputPath)
  43. .resize(smallWidth, smallHeight, { fit: 'fill' })
  44. .toBuffer();
  45. // 再放大回原尺寸
  46. return sharp(smallBuffer)
  47. .resize(width, height, { fit: 'fill', kernel: 'nearest' })
  48. .toBuffer();
  49. }
  50. // 提取颜色并按频率排序
  51. async function extractColors(imageBuffer, pixelSize, originalWidth, originalHeight) {
  52. // 计算缩小后的尺寸
  53. const smallWidth = Math.ceil(originalWidth / pixelSize);
  54. const smallHeight = Math.ceil(originalHeight / pixelSize);
  55. // 缩小图片以获取马赛克块的颜色
  56. const smallBuffer = await sharp(imageBuffer)
  57. .resize(smallWidth, smallHeight, { fit: 'fill' })
  58. .raw()
  59. .toBuffer({ resolveWithObject: true });
  60. const { data, info } = smallBuffer;
  61. const { width, height, channels } = info;
  62. // 颜色映射: 颜色值 -> 出现次数
  63. const colorMap = new Map();
  64. // 遍历像素
  65. for (let y = 0; y < height; y++) {
  66. for (let x = 0; x < width; x++) {
  67. const idx = (y * width + x) * channels;
  68. const r = data[idx];
  69. const g = data[idx + 1];
  70. const b = data[idx + 2];
  71. // 创建颜色键
  72. const colorKey = `rgb(${r},${g},${b})`;
  73. // 更新颜色映射
  74. if (colorMap.has(colorKey)) {
  75. colorMap.set(colorKey, colorMap.get(colorKey) + 1);
  76. } else {
  77. colorMap.set(colorKey, 1);
  78. }
  79. }
  80. }
  81. // 转换为数组并排序
  82. const sortedColors = Array.from(colorMap.entries())
  83. .map(([color, count]) => ({ color, count }))
  84. .sort((a, b) => b.count - a.count);
  85. return sortedColors;
  86. }
  87. // 生成HTML报告
  88. async function generateReport(colorData) {
  89. // 创建HTML内容
  90. const htmlContent = `
  91. <!DOCTYPE html>
  92. <html lang="zh-CN">
  93. <head>
  94. <meta charset="UTF-8">
  95. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  96. <title>图片颜色分析报告</title>
  97. <style>
  98. body {
  99. font-family: 'Arial', sans-serif;
  100. line-height: 1.6;
  101. color: #333;
  102. max-width: 1200px;
  103. margin: 0 auto;
  104. padding: 20px;
  105. }
  106. h1, h2 {
  107. color: #2c3e50;
  108. }
  109. .image-container {
  110. display: flex;
  111. justify-content: space-between;
  112. margin-bottom: 30px;
  113. flex-wrap: wrap;
  114. }
  115. .image-box {
  116. width: 48%;
  117. margin-bottom: 20px;
  118. }
  119. .image-box img {
  120. max-width: 100%;
  121. height: auto;
  122. border: 1px solid #ddd;
  123. }
  124. .color-cards {
  125. display: flex;
  126. flex-wrap: wrap;
  127. gap: 10px;
  128. margin-top: 20px;
  129. }
  130. .color-card {
  131. width: 100px;
  132. height: 100px;
  133. display: flex;
  134. flex-direction: column;
  135. align-items: center;
  136. justify-content: center;
  137. border-radius: 5px;
  138. color: white;
  139. text-shadow: 0 0 2px #000;
  140. font-size: 12px;
  141. position: relative;
  142. }
  143. .color-info {
  144. background: rgba(0,0,0,0.7);
  145. padding: 3px 6px;
  146. border-radius: 3px;
  147. }
  148. .color-distribution {
  149. margin-top: 30px;
  150. }
  151. .color-bar {
  152. height: 30px;
  153. margin: 5px 0;
  154. display: flex;
  155. }
  156. .color-bar-segment {
  157. height: 100%;
  158. display: flex;
  159. align-items: center;
  160. justify-content: center;
  161. color: white;
  162. text-shadow: 0 0 2px #000;
  163. font-size: 12px;
  164. }
  165. </style>
  166. </head>
  167. <body>
  168. <h1>图片颜色分析报告</h1>
  169. <div class="image-container">
  170. <div class="image-box">
  171. <h2>原始图片</h2>
  172. <img src="参考图.png" alt="原始图片">
  173. </div>
  174. <div class="image-box">
  175. <h2>马赛克图片</h2>
  176. <img src="mosaic.png" alt="马赛克图片">
  177. </div>
  178. </div>
  179. <h2>颜色分布</h2>
  180. <p>从马赛克图中提取的主要颜色(按出现频率排序):</p>
  181. <div class="color-cards">
  182. ${colorData.slice(0, 20).map((item, index) => `
  183. <div class="color-card" style="background-color: ${item.color}">
  184. <div class="color-info">
  185. ${item.color}<br>
  186. 频率: ${((item.count / colorData.reduce((sum, c) => sum + c.count, 0)) * 100).toFixed(1)}%
  187. </div>
  188. </div>
  189. `).join('')}
  190. </div>
  191. <div class="color-distribution">
  192. <h2>颜色频率分布图</h2>
  193. <div class="color-bar">
  194. ${colorData.slice(0, 10).map(item => {
  195. const percentage = (item.count / colorData.reduce((sum, c) => sum + c.count, 0)) * 100;
  196. return `<div class="color-bar-segment" style="background-color: ${item.color}; width: ${percentage}%;">${percentage.toFixed(1)}%</div>`;
  197. }).join('')}
  198. </div>
  199. </div>
  200. </body>
  201. </html>
  202. `;
  203. // 写入HTML文件
  204. await fs.promises.writeFile(CONFIG.outputReport, htmlContent);
  205. }
  206. // 运行主函数
  207. main();