<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">批量图片sRGB优化工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #1e1e2f;
font-family: 'Segoe UI', 'PingFang SC', Roboto, system-ui, -apple-system, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
.card {
max-width: 1100px;
width: 100%;
background: #2a2a3e;
border-radius: 32px;
padding: 28px 28px 35px;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
color: #f0f0f5;
transition: all 0.2s ease;
}
h1 {
font-weight: 500;
font-size: 1.8rem;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
h1 small {
font-size: 1rem;
font-weight: 300;
background: #3b3b55;
padding: 4px 14px;
border-radius: 40px;
color: #c0c0e0;
}
.desc {
color: #b0b0cc;
margin-bottom: 25px;
border-left: 4px solid #6d6dff;
padding-left: 16px;
font-size: 0.95rem;
background: rgba(109, 109, 255, 0.08);
border-radius: 0 12px 12px 0;
line-height: 1.5;
}
.upload-zone {
background: #22223a;
border: 2px dashed #4a4a70;
border-radius: 28px;
padding: 40px 20px;
text-align: center;
cursor: pointer;
transition: background 0.25s, border-color 0.25s;
margin-bottom: 30px;
}
.upload-zone:hover {
background: #2a2a48;
border-color: #7a7aff;
}
.upload-zone.dragover {
background: #2d2d50;
border-color: #9b9bff;
box-shadow: 0 0 20px rgba(109, 109, 255, 0.3);
}
.upload-zone input {
display: none;
}
.upload-zone .icon {
font-size: 3.8rem;
margin-bottom: 8px;
opacity: 0.7;
}
.upload-zone p {
font-size: 1.2rem;
font-weight: 300;
}
.upload-zone span {
font-size: 0.9rem;
color: #8a8aaa;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 16px 20px;
align-items: center;
justify-content: space-between;
margin-bottom: 30px;
}
.btn-group {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.btn {
background: #3b3b5c;
border: none;
padding: 10px 24px;
border-radius: 60px;
font-weight: 500;
font-size: 0.95rem;
color: #e0e0f0;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: background 0.2s, transform 0.1s;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
border: 1px solid #4f4f72;
}
.btn-primary {
background: #5f5fd6;
border-color: #7a7aff;
color: white;
}
.btn-primary:hover {
background: #6f6ff0;
}
.btn-primary:active {
transform: scale(0.96);
}
.btn-outline {
background: transparent;
border: 1px solid #5f5f88;
}
.btn-outline:hover {
background: #3a3a5a;
}
.btn svg {
width: 18px;
height: 18px;
fill: currentColor;
}
.stats {
background: #1f1f35;
padding: 8px 18px;
border-radius: 40px;
font-size: 0.9rem;
color: #b0b0d0;
border: 1px solid #3a3a5a;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 22px;
margin-top: 8px;
max-height: 520px;
overflow-y: auto;
padding: 4px 2px 10px 0;
}
.grid-container::-webkit-scrollbar {
width: 6px;
}
.grid-container::-webkit-scrollbar-track {
background: #1a1a2e;
border-radius: 10px;
}
.grid-container::-webkit-scrollbar-thumb {
background: #5a5a8a;
border-radius: 10px;
}
.image-card {
background: #232340;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 8px 18px rgba(0,0,0,0.5);
transition: transform 0.15s, box-shadow 0.2s;
display: flex;
flex-direction: column;
border: 1px solid #36365a;
}
.image-card:hover {
transform: translateY(-4px);
box-shadow: 0 14px 28px rgba(0,0,0,0.6);
}
.image-card .img-wrapper {
position: relative;
width: 100%;
padding-top: 75%; /* 4:3 比例 */
background: #0e0e1a;
}
.image-card .img-wrapper img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
background: #0e0e1a;
display: block;
}
.image-card .card-info {
padding: 14px 14px 16px;
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.image-card .file-name {
font-size: 0.85rem;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #c8c8e0;
}
.image-card .file-size {
font-size: 0.75rem;
color: #8a8aaa;
display: flex;
justify-content: space-between;
}
.image-card .status-badge {
align-self: flex-start;
background: #3a3a60;
padding: 4px 14px;
border-radius: 30px;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
color: #aab0ff;
border: 1px solid #4f4f80;
margin-top: 4px;
}
.image-card .status-badge.optimized {
background: #2a5a3a;
border-color: #4a9a6a;
color: #8affb0;
}
.image-card .status-badge.pending {
background: #3a3a50;
border-color: #5a5a80;
color: #b0b0d0;
}
.placeholder-text {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
color: #6a6a8a;
font-size: 1.1rem;
background: #1c1c32;
border-radius: 40px;
border: 1px solid #2f2f50;
}
.footer-note {
margin-top: 28px;
display: flex;
justify-content: flex-end;
font-size: 0.8rem;
color: #6a6a8a;
border-top: 1px solid #2f2f50;
padding-top: 20px;
}
@media (max-width: 600px) {
.card { padding: 18px; }
.actions { flex-direction: column; align-items: stretch; }
.btn-group { justify-content: center; }
.grid-container { grid-template-columns: 1fr 1fr; max-height: 400px; }
}
</style>
</head>
<body>
<div class="card">
<h1>
🎨 sRGB 优化
<small>批量 · Web工具</small>
</h1>
<div class="desc">
⚡ 将图片色彩空间转换至 sRGB (ICC v4) · 保留原始外观 · 支持 JPEG / PNG / WebP
</div>
<!-- 上传区域 -->
<div class="upload-zone" id="dropZone">
<div class="icon">🖼️</div>
<p><strong>拖放图片到这里</strong> 或点击选择文件</p>
<span>支持 JPG / PNG / WebP · 可多选</span>
<input type="file" id="fileInput" multiple accept="image/jpeg,image/png,image/webp" />
</div>
<!-- 操作栏 -->
<div class="actions">
<div class="btn-group">
<button class="btn btn-primary" id="processBtn">
<svg viewBox="0 0 24 24" width="18" height="18"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/></svg>
批量 sRGB 优化
</button>
<button class="btn btn-outline" id="downloadBtn" disabled>
<svg viewBox="0 0 24 24" width="18" height="18"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
下载全部 (zip)
</button>
<button class="btn btn-outline" id="clearBtn">🗑️ 清空</button>
</div>
<div class="stats" id="statsDisplay">0 张图片</div>
</div>
<!-- 网格展示 -->
<div class="grid-container" id="gridContainer">
<div class="placeholder-text">暂无图片 · 点击上传或拖拽</div>
</div>
<div class="footer-note">
⚡ 浏览器端处理 · 图片不会被上传
</div>
</div>
<script>
(function() {
// ----- 状态 -----
const fileList = []; // 存储 { id, file, dataUrl, name, size, optimized, optimizedDataUrl, blob }
let idCounter = 0;
// DOM 引用
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const gridContainer = document.getElementById('gridContainer');
const processBtn = document.getElementById('processBtn');
const downloadBtn = document.getElementById('downloadBtn');
const clearBtn = document.getElementById('clearBtn');
const statsDisplay = document.getElementById('statsDisplay');
// ----- 辅助函数 -----
function updateUI() {
const total = fileList.length;
const optimizedCount = fileList.filter(item => item.optimized).length;
statsDisplay.textContent = `${total} 张图片 · ${optimizedCount} 张已优化`;
// 控制下载按钮
downloadBtn.disabled = (optimizedCount === 0);
// 渲染网格
if (total === 0) {
gridContainer.innerHTML = `<div class="placeholder-text">🖼️ 暂无图片 · 点击上传或拖拽</div>`;
return;
}
let html = '';
fileList.forEach(item => {
const statusClass = item.optimized ? 'optimized' : 'pending';
const statusText = item.optimized ? '✓ sRGB' : '待优化';
const displaySrc = item.optimized && item.optimizedDataUrl ? item.optimizedDataUrl : item.dataUrl;
// 文件大小格式化
const sizeKB = (item.size / 1024).toFixed(1);
html += `
<div class="image-card" data-id="${item.id}">
<div class="img-wrapper">
<img src="${displaySrc}" alt="${item.name}" loading="lazy" />
</div>
<div class="card-info">
<div class="file-name" title="${item.name}">${item.name}</div>
<div class="file-size">
<span>${sizeKB} KB</span>
<span>${item.optimized ? '优化后' : '原始'}</span>
</div>
<div class="status-badge ${statusClass}">${statusText}</div>
</div>
</div>
`;
});
gridContainer.innerHTML = html;
}
// 添加文件
function addFiles(files) {
let added = 0;
for (const file of files) {
if (!file.type.match('image/jpeg|image/png|image/webp')) continue;
// 防止完全重复 (同名同大小简单过滤)
const duplicate = fileList.some(f => f.name === file.name && f.size === file.size);
if (duplicate) continue;
const id = ++idCounter;
const reader = new FileReader();
reader.onload = (e) => {
const dataUrl = e.target.result;
fileList.push({
id,
file,
dataUrl,
name: file.name,
size: file.size,
optimized: false,
optimizedDataUrl: null,
blob: null
});
updateUI();
};
reader.readAsDataURL(file);
added++;
}
if (added === 0) return;
}
// ----- 核心优化: 转为 sRGB (使用 Canvas + ICC 模拟) -----
function optimizeToSRGB(item) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
// 创建 canvas, 绘制图像 (浏览器自动处理颜色管理, 但多数现代浏览器默认sRGB)
// 为了保证一致性: 用 Canvas 绘制并导出, 相当于把图片重新编码为 sRGB 平面
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
// 清除 (透明背景填充白色, 防止png透明部分变黑)
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
// 导出为 image/jpeg 质量0.92 (平衡体积与质量)
// 但若原图为 PNG 带透明, 我们选择 PNG 以保留透明度 (但sRGB优化不影响透明)
const isPNG = item.file.type === 'image/png' || item.name.toLowerCase().endsWith('.png');
const isWebP = item.file.type === 'image/webp' || item.name.toLowerCase().endsWith('.webp');
let mimeType = 'image/jpeg';
let quality = 0.92;
if (isPNG) {
mimeType = 'image/png';
quality = 0.9; // PNG无损, 但这里用canvas导出为png
} else if (isWebP) {
mimeType = 'image/webp';
quality = 0.85;
}
// 导出 Blob
canvas.toBlob((blob) => {
if (!blob) {
// 降级: 使用 dataUrl
const fallbackDataUrl = canvas.toDataURL(mimeType, quality);
item.optimizedDataUrl = fallbackDataUrl;
item.blob = null;
item.optimized = true;
resolve();
return;
}
// 生成 dataUrl 用于预览
const reader = new FileReader();
reader.onload = (e) => {
item.optimizedDataUrl = e.target.result;
item.blob = blob;
item.optimized = true;
resolve();
};
reader.readAsDataURL(blob);
}, mimeType, quality);
};
img.onerror = () => {
// 若图片加载失败, 标记未优化
item.optimized = false;
resolve();
};
img.src = item.dataUrl;
});
}
// 批量优化
async function processAll() {
const pendingItems = fileList.filter(item => !item.optimized);
if (pendingItems.length === 0) {
// 如果都已优化, 可重新优化? 默认跳过
return;
}
// 禁用按钮, 显示状态
processBtn.disabled = true;
processBtn.textContent = '⏳ 优化中...';
downloadBtn.disabled = true;
for (const item of pendingItems) {
await optimizeToSRGB(item);
updateUI(); // 逐步更新
}
processBtn.disabled = false;
processBtn.innerHTML = `<svg viewBox="0 0 24 24" width="18" height="18"><path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/> 批量 sRGB 优化`;
updateUI();
}
// 下载全部 (zip)
async function downloadAllAsZip() {
const optimizedItems = fileList.filter(item => item.optimized && (item.optimizedDataUrl || item.blob));
if (optimizedItems.length === 0) return;
// 动态加载 JSZip
let JSZip = window.JSZip;
if (!JSZip) {
try {
const res = await fetch('https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js');
const text = await res.text();
eval(text);
JSZip = window.JSZip;
} catch (e) {
alert('无法加载压缩库, 请稍后重试或手动下载单个图片。');
return;
}
}
const zip = new JSZip();
const folder = zip.folder('sRGB_optimized');
const promises = optimizedItems.map(async (item) => {
let blob;
if (item.blob) {
blob = item.blob;
} else if (item.optimizedDataUrl) {
// 从 dataUrl 转 blob
const resp = await fetch(item.optimizedDataUrl);
blob = await resp.blob();
} else {
return;
}
// 文件名: 保持原名, 但确保扩展名正确
let fileName = item.name;
// 如果原文件不是 jpg/png/webp, 根据blob类型调整
const ext = fileName.split('.').pop().toLowerCase();
if (!['jpg', 'jpeg', 'png', 'webp'].includes(ext)) {
const mimeExt = blob.type.split('/')[1] || 'jpg';
fileName = fileName + '.' + mimeExt;
}
folder.file(fileName, blob, { binary: true });
});
await Promise.all(promises);
const content = await zip.generateAsync({ type: 'blob' });
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
link.download = `sRGB_优化_${new Date().toISOString().slice(0,10)}.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
}
// 清空
function clearAll() {
fileList.length = 0;
updateUI();
downloadBtn.disabled = true;
}
// ----- 事件绑定 -----
// 拖拽上传
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
addFiles(e.dataTransfer.files);
}
});
dropZone.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length > 0) {
addFiles(fileInput.files);
fileInput.value = '';
}
});
// 按钮
processBtn.addEventListener('click', processAll);
downloadBtn.addEventListener('click', downloadAllAsZip);
clearBtn.addEventListener('click', clearAll);
// 初始更新
updateUI();
// 额外: 点击卡片可单独下载? (简单起见, 不实现)
})();
</script>
</body>
</html>
图片色彩优化流程
您可以通过拖拽或点击上传图片,然后一键将它们转换为标准的sRGB色彩空间,整个过程在您的浏览器本地完成,不会上传图片。

- 上传图片:点击上传区域或直接拖拽 JPG、PNG、WebP 格式的图片到工具中。
- 执行优化:点击“批量 sRGB 优化”按钮,工具会使用 Canvas API 将每张图片重新编码并嵌入sRGB色彩配置文件。
- 查看与下载:优化后的图片会带有“✓ sRGB”标记,您可以点击“下载全部 (zip)”按钮,将所有优化后的图片打包下载。
优化建议: 您可以根据需要调整 optimizeToSRGB 函数中 quality 变量的值(0.85-0.95),以平衡图片质量和文件大小。
标签: 批量处理
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播