Перейти к основному содержимому
clawam tools
Инструменты Документы Daoark EN Главная Главная / Инструменты для изображений › Главная manifest.json > 16) & 255, g: (n >> 8) & 255, b: n & 255 }; } function parseColor(s) { s = s.trim(); var m = s.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i); if (m) return { r: parseInt(m[1]), g: parseInt(m[2]), b: parseInt(m[3]) }; return hexToRgb(s); } function rgbToHex(c) { function h(v) { return ('0' + v.toString(16)).slice(-2); } return '#' + h(c.r) + h(c.g) + h(c.b); } function rgbToHsl(c) { var r = c.r / 255, g = c.g / 255, b = c.b / 255; var mx = Math.max(r, g, b), mn = Math.min(r, g, b); var h, s, l = (mx + mn) / 2; if (mx === mn) { h = s = 0; } else { var d = mx - mn; s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn); switch (mx) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return { h: h * 360, s: s * 100, l: l * 100 }; } function hslToRgb(hsl) { var h = hsl.h / 360, s = hsl.s / 100, l = hsl.l / 100; var r, g, b; if (s === 0) { r = g = b = l; } else { var hue2rgb = function(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) }; } // 简单 K-means(像素采样) function kmeans(pixels, k) { if (pixels.length === 0) return []; var centroids = []; for (var i = 0; i < k; i++) centroids.push(pixels[Math.floor(Math.random() * pixels.length)]); for (var iter = 0; iter < 10; iter++) { var clusters = []; for (var j = 0; j < k; j++) clusters.push([]); for (var p = 0; p < pixels.length; p++) { var bestD = Infinity, bestC = 0; for (var c = 0; c < k; c++) { var dx = pixels[p].r - centroids[c].r, dy = pixels[p].g - centroids[c].g, dz = pixels[p].b - centroids[c].b; var d = dx * dx + dy * dy + dz * dz; if (d < bestD) { bestD = d; bestC = c; } } clusters[bestC].push(pixels[p]); } for (var c2 = 0; c2 < k; c2++) { if (clusters[c2].length > 0) { var sr = 0, sg = 0, sb = 0; for (var p2 = 0; p2 < clusters[c2].length; p2++) { sr += clusters[c2][p2].r; sg += clusters[c2][p2].g; sb += clusters[c2][p2].b; } centroids[c2] = { r: sr / clusters[c2].length, g: sg / clusters[c2].length, b: sb / clusters[c2].length }; } } } // 按出现次数排序 var counts = clusters.map(function(c) { return c.length; }); var order = []; for (var i = 0; i < k; i++) order.push(i); order.sort(function(a, b) { return counts[b] - counts[a]; }); return order.map(function(i) { return { rgb: centroids[i], count: counts[i] }; }); } function genScheme(baseRgb, scheme) { var hsl = rgbToHsl(baseRgb); var out = [baseRgb]; if (scheme === 'complement') { out.push(hslToRgb({ h: (hsl.h + 180) % 360, s: hsl.s, l: hsl.l })); } else if (scheme === 'triadic') { out.push(hslToRgb({ h: (hsl.h + 120) % 360, s: hsl.s, l: hsl.l })); out.push(hslToRgb({ h: (hsl.h + 240) % 360, s: hsl.s, l: hsl.l })); } else if (scheme === 'analogous') { out.push(hslToRgb({ h: (hsl.h + 30) % 360, s: hsl.s, l: hsl.l })); out.push(hslToRgb({ h: (hsl.h + 330) % 360, s: hsl.s, l: hsl.l })); } else if (scheme === 'split') { out.push(hslToRgb({ h: (hsl.h + 150) % 360, s: hsl.s, l: hsl.l })); out.push(hslToRgb({ h: (hsl.h + 210) % 360, s: hsl.s, l: hsl.l })); } else if (scheme === 'tetradic') { out.push(hslToRgb({ h: (hsl.h + 90) % 360, s: hsl.s, l: hsl.l })); out.push(hslToRgb({ h: (hsl.h + 180) % 360, s: hsl.s, l: hsl.l })); out.push(hslToRgb({ h: (hsl.h + 270) % 360, s: hsl.s, l: hsl.l })); } else if (scheme === 'monochrome') { out.push(hslToRgb({ h: hsl.h, s: hsl.s, l: Math.min(95, hsl.l + 20) })); out.push(hslToRgb({ h: hsl.h, s: hsl.s, l: Math.max(5, hsl.l - 20) })); out.push(hslToRgb({ h: hsl.h, s: hsl.s, l: Math.max(5, hsl.l - 40) })); } return out; } function swatchHtml(colors) { var html = '
'; for (var i = 0; i < colors.length; i++) { var c = colors[i]; var hex = rgbToHex(c); html += '
' + hex + '
rgb(' + Math.round(c.r) + ',' + Math.round(c.g) + ',' + Math.round(c.b) + ')
'; } html += '
'; return html; } var origImg = null; $('file').addEventListener('change', function(e) { var f = e.target.files[0]; if (!f) return; var r = new FileReader(); r.onload = function(ev) { origImg = new Image(); origImg.onload = function() { showResult('

已加载: ' + origImg.width + ' × ' + origImg.height + '

', '图片已加载'); }; origImg.src = ev.target.result; }; r.readAsDataURL(f); }); function extract() { if (!origImg) { showError('请先选图片'); return; } var k = parseInt(getVal('k'), 10); if (!(k >= 3 && k <= 8)) { showError('k 3-8'); return; } var canvas = document.createElement('canvas'); var maxW = 200; var scale = Math.min(1, maxW / origImg.width); canvas.width = Math.round(origImg.width * scale); canvas.height = Math.round(origImg.height * scale); var ctx = canvas.getContext('2d'); ctx.drawImage(origImg, 0, 0, canvas.width, canvas.height); var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); var pixels = []; for (var i = 0; i < imgData.data.length; i += 4) { pixels.push({ r: imgData.data[i], g: imgData.data[i + 1], b: imgData.data[i + 2] }); } // 降采样 if (pixels.length > 5000) { var step = Math.ceil(pixels.length / 5000); var sampled = []; for (var j = 0; j < pixels.length; j += step) sampled.push(pixels[j]); pixels = sampled; } var result = kmeans(pixels, k); var colors = result.map(function(r) { return r.rgb; }); showResult(swatchHtml(colors), k + ' 个主色(按出现频次)'); } function scheme() { var baseStr = getVal('base'); var base; if (baseStr) { base = parseColor(baseStr); if (!base) { showError('基准色格式错(用 #ff5733 或 rgb(255,87,51))'); return; } } else if (origImg) { // 用主色的第一个 var canvas = document.createElement('canvas'); canvas.width = origImg.width; canvas.height = origImg.height; var ctx = canvas.getContext('2d'); ctx.drawImage(origImg, 0, 0); var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); var colors = {}; for (var i = 0; i < imgData.data.length; i += 4) { var key = imgData.data[i] + ',' + imgData.data[i + 1] + ',' + imgData.data[i + 2]; colors[key] = (colors[key] || 0) + 1; } var best = '', bestC = 0; for (var k in colors) if (colors[k] > bestC) { bestC = colors[k]; best = k; } var parts = best.split(','); base = { r: parseInt(parts[0]), g: parseInt(parts[1]), b: parseInt(parts[2]) }; } else { showError('请输入基准色或选图片'); return; } var schemeType = getVal('scheme'); if (schemeType === 'none') { showError('请选配色方案'); return; } var out = genScheme(base, schemeType); showResult(swatchHtml(out), schemeType + ' 配色,基准 ' + rgbToHex(base)); } function exportCode() { var text = $('result-meta').textContent; var colors = []; var swatches = $('result-content').querySelectorAll('div[style*="width:120px"]'); swatches.forEach(function(s) { var t = s.textContent; var m = t.match(/#[0-9a-fA-F]{6}/); if (m) colors.push(m[0]); }); if (colors.length === 0) { showError('请先生成颜色'); return; } var css = ':root {\n' + colors.map(function(c, i) { return ' --color-' + (i + 1) + ': ' + c + ';'; }).join('\n') + '\n}'; var tw = colors.map(function(c, i) { return ' primary-' + (i + 1) + ': "'' + c + '"', ';'; }).join('\n'); showResult('--- CSS 变量 ---\n' + css + '\n\n--- Tailwind config ---\ntheme: {\n extend: {\n colors: {\n' + tw + '\n }\n }\n}', '已导出 ' + colors.length + ' 个颜色'); } $('btn-extract').addEventListener('click', extract); $('btn-scheme').addEventListener('click', scheme); $('btn-export').addEventListener('click', exportCode); $('btn-clear').addEventListener('click', function() { $('file').value = ''; $('base').value = ''; origImg = null; var ra = $('result-area'); ra.hidden = true; ra.style.display = 'none'; var em = $('error-msg'); em.hidden = true; em.style.display = 'none'; }); })();