图片颜色提取器 - 从图片提取调色板 在线
从任意图片提取主色调并生成调色板。获取HEX、RGB和HSL值 — 100%客户端处理,图片不上传。
常见问题
什么是图像颜色提取器,为什么需要它?
图像颜色提取器分析上传图像的像素并识别最主要的颜色,生成颜色调色板。对于需要从照片、艺术作品或品牌图像中派生一致色彩方案的设计师非常有用。该工具无需使用吸管手动采样颜色,而是自动识别最具代表性的颜色,并以网页就绪格式(HEX、RGB、HSL)提供,可直接用于CSS、设计工具或品牌指南。
如何使用这个图像颜色提取工具?
1. 将图像拖放到上传区域(或点击浏览文件)。2. 工具自动提取主要颜色并显示为调色板。3. 使用调色板大小滑块调整颜色数量(2-16种颜色)。4. 点击任何色块复制其HEX、RGB或HSL值。5. 使用导出选项将整个调色板复制为CSS变量、JSON或纯文本。6. 可选择使用排序控件按频率、色调或亮度排序颜色。
我的图像数据安全吗?图像会上传到服务器吗?
您的图像100%安全,永远不会离开您的浏览器。所有颜色提取都使用HTML5 Canvas API在客户端执行。该工具直接在浏览器中读取图像的像素数据,在本地应用颜色量化算法并显示结果。没有任何图像数据传输到任何服务器,使该工具完全私密。
使用什么颜色量化算法?
该工具使用Median Cut算法,这是一种成熟的颜色量化技术。它通过递归地将图像的颜色空间划分为更小的区域来工作,在范围最宽的通道(红色、绿色或蓝色)的中值处进行分割。然后对每个区域取平均值以产生单个代表颜色。Median Cut快速、确定性强,能生成准确表示主要颜色的视觉效果良好的调色板。
支持哪些图像格式?
该工具支持PNG、JPEG(JPG)、WebP和GIF图像,文件大小最大10MB。PNG、WebP和GIF图像中的透明像素在颜色提取时会自动忽略,不会影响调色板。对于GIF图像,仅分析第一个(静态)帧。
可以从图像中提取多少种颜色?
可以提取2到16种颜色。默认值为8种颜色,为大多数用例提供了良好的平衡。对于简单图像或极简调色板,4-6种颜色效果很好。对于具有许多不同颜色区域的复杂照片,10-16种颜色将捕获更多细微差别。调整滑块时提取会实时更新。
按频率、色调和亮度排序有什么区别?
频率(默认)按颜色在图像中占据的面积排序,最主要的颜色排在最前面。色调按颜色在色轮上的位置(0-360度)排序,创建从红色到橙色、黄色、绿色、蓝色和紫色的彩虹般序列。亮度根据HSL明度值从最亮到最暗排序,适用于创建渐变或识别对比度范围。
可以导出颜色调色板用于代码中吗?
是的。该工具提供多种针对开发者优化的导出格式:CSS变量生成 :root { --color-1: #FF5733; ... },可直接用于CSS。JSON导出包含hex、rgb和hsl值的对象数组,用于编程使用。纯文本提供每行一个HEX代码的简单列表。
代码示例
// Extract dominant colors from an image using Canvas API + Median Cut
function extractColorsFromImage(imageSource, colorCount = 8) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const maxPixels = 10000;
const scale = Math.min(1, Math.sqrt(maxPixels / (img.width * img.height)));
const width = Math.max(1, Math.floor(img.width * scale));
const height = Math.max(1, Math.floor(img.height * scale));
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
const pixels = [];
for (let i = 0; i < imageData.data.length; i += 4) {
if (imageData.data[i + 3] < 128) continue;
pixels.push({
r: imageData.data[i],
g: imageData.data[i + 1],
b: imageData.data[i + 2],
});
}
const colors = medianCut(pixels, colorCount);
resolve(colors);
};
img.onerror = () => reject(new Error("Failed to load image"));
if (imageSource instanceof File) {
img.src = URL.createObjectURL(imageSource);
} else {
img.src = imageSource;
}
});
}
function medianCut(pixels, targetCount) {
if (pixels.length === 0) return [];
let buckets = [pixels];
while (buckets.length < targetCount) {
let maxRange = -1, maxIndex = 0, splitChannel = "r";
for (let i = 0; i < buckets.length; i++) {
if (buckets[i].length < 2) continue;
for (const ch of ["r", "g", "b"]) {
const values = buckets[i].map((p) => p[ch]);
const range = Math.max(...values) - Math.min(...values);
if (range > maxRange) {
maxRange = range;
maxIndex = i;
splitChannel = ch;
}
}
}
if (maxRange <= 0) break;
const toSplit = buckets[maxIndex];
toSplit.sort((a, b) => a[splitChannel] - b[splitChannel]);
const mid = Math.floor(toSplit.length / 2);
buckets.splice(maxIndex, 1, toSplit.slice(0, mid), toSplit.slice(mid));
}
const totalPixels = pixels.length;
return buckets.map((bucket) => {
const r = Math.round(bucket.reduce((s, p) => s + p.r, 0) / bucket.length);
const g = Math.round(bucket.reduce((s, p) => s + p.g, 0) / bucket.length);
const b = Math.round(bucket.reduce((s, p) => s + p.b, 0) / bucket.length);
const hex = "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("").toUpperCase();
return { hex, rgb: { r, g, b }, percentage: ((bucket.length / totalPixels) * 100).toFixed(1) };
});
}
// Usage
const input = document.querySelector('input[type="file"]');
input.addEventListener("change", async (e) => {
const file = e.target.files[0];
const colors = await extractColorsFromImage(file, 8);
colors.forEach((c) => console.log(`${c.hex} - ${c.percentage}%`));
});