<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">🎨 图片底色转换工具</title>
<style>
* {
box-sizing: border-box;
}
body {
background: linear-gradient(145deg, #0f172a 0%, #1e293b 100%);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
min-height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.card {
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 32px;
padding: 30px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), inset 0 1px 2px rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.15);
max-width: 950px;
width: 100%;
transition: all 0.2s ease;
}
h1 {
margin: 0 0 20px 0;
font-size: 2.2rem;
font-weight: 600;
color: #f0f9ff;
text-align: center;
letter-spacing: -0.5px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
text-shadow: 0 4px 10px rgba(0,0,0,0.3);
}
h1 span {
background: #38bdf8;
width: 14px;
height: 14px;
border-radius: 50%;
box-shadow: 0 0 20px #38bdf8;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: flex-end;
justify-content: center;
background: rgba(15, 23, 42, 0.7);
border-radius: 60px;
padding: 20px 25px;
margin-bottom: 30px;
border: 1px solid rgba(148, 163, 184, 0.2);
}
.color-picker-wrap {
display: flex;
align-items: center;
gap: 12px;
background: rgba(51, 65, 85, 0.6);
padding: 8px 18px;
border-radius: 40px;
backdrop-filter: blur(4px);
}
.color-picker-wrap label {
color: #cbd5e1;
font-weight: 500;
font-size: 0.9rem;
letter-spacing: 0.3px;
}
#bgColor {
width: 60px;
height: 60px;
border-radius: 30px;
border: 2px solid #fff;
background: #ffffff;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
transition: 0.1s;
}
#bgColor:hover {
transform: scale(1.05);
}
.action-buttons {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.btn {
background: #ffffff10;
border: 1px solid #ffffff30;
padding: 12px 28px;
border-radius: 60px;
font-size: 1rem;
font-weight: 500;
color: #f8fafc;
cursor: pointer;
transition: 0.2s;
display: flex;
align-items: center;
gap: 8px;
backdrop-filter: blur(4px);
letter-spacing: 0.3px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.btn-primary {
background: #38bdf8;
border-color: #7dd3fc;
color: #0c1a2a;
font-weight: 600;
box-shadow: 0 8px 20px rgba(56, 189, 248, 0.3);
}
.btn-primary:hover {
background: #0ea5e9;
transform: translateY(-2px);
}
.btn-outline:hover {
background: #ffffff20;
border-color: #ffffff70;
}
.btn:active {
transform: scale(0.96);
}
.canvas-section {
display: flex;
flex-direction: column;
align-items: center;
}
.canvas-container {
background-color: rgba(255, 255, 255, 0.03);
border-radius: 28px;
padding: 12px;
border: 1px dashed #afc3d8;
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
width: 100%;
display: flex;
justify-content: center;
}
canvas {
width: 100%;
height: auto;
max-width: 800px;
max-height: 500px;
border-radius: 16px;
background: repeating-conic-gradient(#2a3b4e 0% 25%, #1e2a3a 0% 50%) 50% / 20px 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
display: block;
transition: box-shadow 0.2s;
}
.upload-area {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
margin-bottom: 10px;
}
.file-label {
background: #2a3b4e;
padding: 14px 24px;
border-radius: 60px;
color: #e2e8f0;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
display: inline-flex;
align-items: center;
gap: 10px;
border: 1px solid #475569;
}
.file-label:hover {
background: #3b4f66;
border-color: #64748b;
}
.file-input {
display: none;
}
.info-text {
color: #94a3b8;
text-align: center;
font-size: 0.9rem;
margin-top: 12px;
border-top: 1px solid #334155;
padding-top: 20px;
letter-spacing: 0.2px;
}
.download-btn {
background: #22c55e;
border: none;
color: #fff;
font-weight: 600;
box-shadow: 0 6px 16px #22c55e40;
}
.download-btn:hover {
background: #16a34a;
}
.filename-badge {
font-size: 0.85rem;
background: #1e293b;
padding: 6px 18px;
border-radius: 60px;
color: #a5d8ff;
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 260px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.filename-badge img {
width: 16px;
height: 16px;
filter: invert(0.8);
}
.split-line {
display: flex;
align-items: center;
gap: 16px;
margin: 16px 0;
}
.split-line span {
background: #1e3a5f;
height: 1px;
flex: 1;
opacity: 0.3;
}
@media (max-width: 700px) {
.toolbar {
flex-direction: column;
align-items: stretch;
border-radius: 32px;
}
.color-picker-wrap, .action-buttons {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="card">
<h1>
<span></span> 底色转换器 <span></span>
</h1>
<!-- 上传 + 工具栏 -->
<div class="toolbar">
<div class="upload-area" style="margin-bottom:0;">
<label for="fileInput" class="file-label">
📂 选择图片
</label>
<input type="file" id="fileInput" class="file-input" accept="image/*">
<span class="filename-badge" id="fileNameDisplay">📄 未选择文件</span>
</div>
<div class="color-picker-wrap">
<label for="bgColor">🎯 目标底色</label>
<input type="color" id="bgColor" value="#ffffff">
</div>
<div class="action-buttons">
<button id="convertBtn" class="btn btn-primary">✨ 转换底色</button>
<button id="downloadBtn" class="btn download-btn">⬇️ 下载图片</button>
</div>
</div>
<!-- Canvas 预览区 -->
<div class="canvas-section">
<div class="canvas-container">
<canvas id="canvas" width="800" height="500"></canvas>
</div>
<div class="info-text">
💡 点击【选择图片】载入本地图像,再选取目标背景色,点击「转换底色」<br>
背景将变为纯色(自动去除白色或浅色背景)
</div>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const fileInput = document.getElementById('fileInput');
const fileNameDisplay = document.getElementById('fileNameDisplay');
const bgColorInput = document.getElementById('bgColor');
const convertBtn = document.getElementById('convertBtn');
const downloadBtn = document.getElementById('downloadBtn');
// 状态: 保存原始图像数据 (ImageData 或 canvas)
let originalCanvas = null; // 存储原始图片的canvas
let hasImage = false;
// 默认画一个示例图片 (渐变+圆,方便演示)
function drawPlaceholder() {
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0, 0, w, h);
// 渐变背景
const grad = ctx.createLinearGradient(0, 0, w*0.6, h);
grad.addColorStop(0, '#faa7c0');
grad.addColorStop(0.5, '#ffe0e9');
grad.addColorStop(1, '#ffffff');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
// 画一些图形
ctx.fillStyle = '#5b8def';
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(w*0.6, h*0.45, 80, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#eb8f8f';
ctx.shadowBlur = 14;
ctx.beginPath();
ctx.arc(w*0.3, h*0.3, 55, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#c0a5fb';
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(w*0.45, h*0.7, 65, 0, Math.PI*2);
ctx.fill();
ctx.shadowBlur = 0;
// 添加文字
ctx.font = 'bold 28px "Segoe UI", sans-serif';
ctx.fillStyle = '#003153';
ctx.fillText('示例图片', 50, 90);
// 保存为原始canvas
originalCanvas = document.createElement('canvas');
originalCanvas.width = w;
originalCanvas.height = h;
const octx = originalCanvas.getContext('2d');
octx.drawImage(canvas, 0, 0);
hasImage = true;
fileNameDisplay.innerHTML = '📄 内置示例';
}
// 重新绘制为当前原始内容 (不改变)
function restoreOriginal() {
if (originalCanvas) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalCanvas, 0, 0);
hasImage = true;
} else {
drawPlaceholder();
}
}
// 初始画占位图
drawPlaceholder();
// ---------- 文件上传 ----------
fileInput.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
// 设置canvas尺寸适配图像比例,但限制最大宽度 800,高度 500
let imgW = img.width;
let imgH = img.height;
const maxW = 800;
const maxH = 500;
// 缩放比例
let ratio = Math.min(maxW/imgW, maxH/imgH, 1);
let scaledW = Math.floor(imgW * ratio);
let scaledH = Math.floor(imgH * ratio);
canvas.width = scaledW;
canvas.height = scaledH;
ctx.clearRect(0, 0, scaledW, scaledH);
// 背景填充为白色,便于后续处理(默认展示白色底)
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, scaledW, scaledH);
ctx.drawImage(img, 0, 0, scaledW, scaledH);
// 保存原始canvas
originalCanvas = document.createElement('canvas');
originalCanvas.width = scaledW;
originalCanvas.height = scaledH;
const octx = originalCanvas.getContext('2d');
octx.drawImage(canvas, 0, 0);
hasImage = true;
// 更新显示文件名
fileNameDisplay.textContent = `📄 ${file.name}`;
// 清掉文件input内部(但保留同文件可选)
fileInput.value = '';
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
// ---------- 转换底色核心算法 ----------
function convertBackground(targetHex) {
if (!hasImage || !originalCanvas) {
alert('请先选择图片');
return;
}
// 获取原始图像数据
const origCtx = originalCanvas.getContext('2d', { willReadFrequently: true });
const origData = origCtx.getImageData(0, 0, originalCanvas.width, originalCanvas.height);
const data = new Uint8ClampedArray(origData.data); // 复制一份进行修改
// 解析目标RGB
let targetR = parseInt(targetHex.substr(1,2), 16);
let targetG = parseInt(targetHex.substr(3,2), 16);
let targetB = parseInt(targetHex.substr(5,2), 16);
const tolerance = 165; // 背景识别容差(大于等于245的像素都视为“浅色/白色”背景区域)
const outputR = targetR;
const outputG = targetG;
const outputB = targetB;
const totalPixels = data.length / 4;
// 第一步:扫描所有像素,找出“背景像素”——即亮度很高且颜色饱和度较低的浅色像素(近似白色、灰色、很浅的彩色)
// 阈值:RGB 每个通道 > 245 或 亮度 > 245 且饱和度低,但更通用:只要三个通道都 > 200且接近无色(最大通道差<30)都视为底色
// 但是这里图片可能含有白色物体,所以只处理“浅色占大部分”的边缘情况,为了简化,我们处理纯白色或接近白色像素。
// 可以调整策略: 将 R,G,B 都大于 245 的像素判定为背景。
const isBackground = new Uint8Array(totalPixels);
for (let i = 0; i < totalPixels; i++) {
const idx = i * 4;
const r = data[idx];
const g = data[idx+1];
const b = data[idx+2];
// 背景像素: 非常亮且饱和度低的颜色 (白色、极浅灰色)
// 增加兼容:所有通道 > 240 或 (亮度>240且max-min<15)
const maxC = Math.max(r, g, b);
const minC = Math.min(r, g, b);
if (r > 240 && g > 240 && b > 240) {
isBackground[i] = 1;
} else if (maxC > 235 && (maxC - minC) < 12) {
isBackground[i] = 1;
}
// 可额外处理接近白色的背景,否则略过
}
// 第二步:将背景像素替换为目标颜色,非背景像素保持不变
for (let i = 0; i < totalPixels; i++) {
if (isBackground[i]) {
const idx = i * 4;
data[idx] = outputR;
data[idx+1] = outputG;
data[idx+2] = outputB;
data[idx+3] = 255; // 不透明
}
}
// 写入canvas
const imageData = new ImageData(data, originalCanvas.width, originalCanvas.height);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
// 注意:转换后originalCanvas不应该更新,因为要保留原图,如果再次转换会丢失原始信息。
// 这里我们保持originalCanvas不变,但canvas预览变为已转换的图像,后续点击下载导出基于canvas即可。
}
// ---------- 事件绑定 ----------
convertBtn.addEventListener('click', function() {
const hexColor = bgColorInput.value; // #ff00aa
if (!hasImage) {
alert('请先选择一张图片');
return;
}
convertBackground(hexColor);
});
// 恢复原始图片 (其实为了用户体验,可以把未修改的原始图片存到originalCanvas,每次转换仅基于originalCanvas)
// 但为了可以在转换后再点转换,我们需要基于原始originalCanvas重建,而不是基于已经被修改的canvas。
// 改进:convertBackground 每次基于 originalCanvas 的图像数据,所以不管canvas现在是什么,都不影响。
// 现在convert函数内使用origData,完美。
// 可添加“重置”按钮?为了体验,可以双击canvas重置,但不做复杂UI,临时加一个监听: 点击文件名区域重置
// 但工具已经够用,用户点击【选择图片】会自动重置。
// 下载
downloadBtn.addEventListener('click', function() {
if (!hasImage) {
alert('没有图片可下载');
return;
}
const link = document.createElement('a');
link.download = `converted_${Date.now()}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
});
// 当点击选择图片后,如果读图失败或者空,就不动。
// 同时添加一个重置小功能:双击预览恢复原始图像
canvas.addEventListener('dblclick', function() {
// 恢复显示originalCanvas
if (originalCanvas) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalCanvas, 0, 0);
hasImage = true;
} else {
drawPlaceholder();
}
});
// 如果文件为空,保留默认示例,同时如果用户没有上传图片但是点击转换,会提示先选择图片(默认示例也是图,可以展示转换效果)
// 但hasImage默认是true,因为drawPlaceholder设置了originalCanvas,可以转换示例图片,很棒。
// 但是目前placeholder生成的是示例,已经可以测试。
// 处理窗口变化预留(无)
// 修改颜色选择器默认白色
// 初始化: 让原始画布清晰
restoreOriginal();
// 如果样例颜色改变,用户再次点击转换,会基于originalCanvas,不受之前转换影响 (因为我们每次convert都从originalCanvas取值)
// 转换后canvas更新的图像不会更新originalCanvas,因此再次点击依旧用原图。
console.log('✅ 图片底色转换脚本已就绪。');
})();
</script>
</body>
</html>
功能与操作说明
您可以轻松上传图片并一键替换其浅色背景,非常适合快速制作纯色背景的图片。

- 图片上传与预览:页面顶部提供“选择图片”按钮,支持从本地加载常见格式的图片,加载后,图片会显示在中央的预览画布上,文件名也会同步更新。
- 核心功能操作:通过颜色选择器选取您想要的目标底色,然后点击“转换底色”按钮,工具会智能识别并替换图片中的白色或浅色背景区域,其他颜色像素将保持不变。
- 结果处理与提示:转换后的图片会立即在画布上预览,您可以使用“下载图片”按钮将其保存为PNG格式,双击预览区域可以随时恢复显示原始图片,方便对比效果。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播