这是一个非常实用的图像处理需求!我来帮你创建一个HTML页面,实现图片的批量反色(负片效果)处理工具。

<!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">图片批量反色工具 · 负片效果</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
background: #f0f2f5;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem 1rem;
color: #1a1a2e;
}
.container {
max-width: 1100px;
width: 100%;
background: white;
border-radius: 24px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.08), 0 8px 16px rgba(0, 0, 0, 0.06);
padding: 2rem 2rem 2.5rem;
transition: all 0.2s;
}
h1 {
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
font-size: 1.8rem;
letter-spacing: -0.01em;
margin-bottom: 0.25rem;
color: #0b0b1e;
}
.subtitle {
color: #5b5b7a;
margin-bottom: 1.8rem;
font-size: 1rem;
border-left: 4px solid #6c5ce7;
padding-left: 1rem;
background: #f8f7ff;
border-radius: 0 8px 8px 0;
line-height: 1.4;
}
.upload-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 2px dashed #c2c7e0;
border-radius: 20px;
padding: 2.5rem 1rem;
background: #fafbff;
transition: border 0.2s, background 0.2s;
cursor: pointer;
}
.upload-area:hover {
border-color: #6c5ce7;
background: #f4f2ff;
}
.upload-area.dragover {
border-color: #6c5ce7;
background: #eeebff;
box-shadow: 0 0 0 4px rgba(108, 92, 231, 0.15);
}
.upload-icon {
font-size: 3.2rem;
line-height: 1;
margin-bottom: 0.6rem;
opacity: 0.7;
}
.upload-text {
font-weight: 500;
font-size: 1.2rem;
color: #2d2d44;
}
.upload-hint {
font-size: 0.9rem;
color: #7a7a9a;
margin-top: 0.3rem;
}
input[type="file"] {
display: none;
}
.action-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px;
margin: 24px 0 20px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0.6rem 1.6rem;
border-radius: 60px;
border: none;
font-weight: 500;
font-size: 0.95rem;
background: #fff;
color: #1a1a2e;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
border: 1px solid #d9dceb;
transition: all 0.15s;
cursor: pointer;
}
.btn-primary {
background: #6c5ce7;
border: 1px solid #6c5ce7;
color: white;
box-shadow: 0 4px 10px rgba(108, 92, 231, 0.25);
}
.btn-primary:hover {
background: #5a4bd1;
box-shadow: 0 6px 14px rgba(108, 92, 231, 0.3);
transform: translateY(-1px);
}
.btn-primary:active {
transform: scale(0.96);
}
.btn-secondary {
background: #f0f2f8;
border-color: #d9dceb;
}
.btn-secondary:hover {
background: #e5e7f0;
}
.btn-danger {
background: #fff0f0;
border-color: #ffc2c2;
color: #b33a3a;
}
.btn-danger:hover {
background: #ffe0e0;
border-color: #ff9f9f;
}
.stats {
margin-left: auto;
font-size: 0.9rem;
color: #5b5b7a;
background: #f4f5fc;
padding: 0.4rem 1.2rem;
border-radius: 40px;
white-space: nowrap;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 20px;
margin: 20px 0 10px;
}
.card {
background: #ffffff;
border-radius: 18px;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.04);
border: 1px solid #eef0f8;
overflow: hidden;
transition: transform 0.1s;
display: flex;
flex-direction: column;
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.06);
}
.card-image {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
background: #f2f2f8;
border-bottom: 1px solid #eff1f9;
}
.card-info {
padding: 10px 12px 12px;
font-size: 0.8rem;
display: flex;
justify-content: space-between;
align-items: center;
background: white;
}
.card-filename {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 110px;
color: #2d2d44;
font-weight: 450;
}
.card-download {
background: transparent;
border: none;
color: #6c5ce7;
font-weight: 500;
font-size: 0.75rem;
padding: 4px 12px;
border-radius: 30px;
background: #f0eeff;
cursor: pointer;
transition: 0.1s;
white-space: nowrap;
}
.card-download:hover {
background: #6c5ce7;
color: white;
}
.empty-message {
grid-column: 1 / -1;
text-align: center;
padding: 3rem 1rem;
color: #8989ae;
background: #f8f9ff;
border-radius: 24px;
font-size: 1rem;
}
.footer-note {
margin-top: 24px;
font-size: 0.85rem;
color: #8b8bae;
text-align: center;
border-top: 1px solid #edf0f8;
padding-top: 20px;
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
@media (max-width: 600px) {
.container {
padding: 1.2rem;
}
.action-bar {
gap: 12px;
}
.stats {
margin-left: 0;
width: 100%;
text-align: center;
}
.gallery {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 14px;
}
h1 {
font-size: 1.5rem;
}
}
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-thumb {
background: #c2c7e0;
border-radius: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>🎞️ 批量反色 · 负片</h1>
<div class="subtitle">拖拽或选择图片 —— 自动生成反色负片效果,支持批量下载</div>
<!-- 上传区域 -->
<div class="upload-area" id="dropZone">
<div class="upload-icon">🖼️</div>
<div class="upload-text">点击或拖放图片到这里</div>
<div class="upload-hint">支持 JPG / PNG / WebP · 批量处理</div>
</div>
<input type="file" id="fileInput" multiple accept="image/png,image/jpeg,image/webp,image/bmp,image/gif,image/avif">
<!-- 操作栏 -->
<div class="action-bar">
<button class="btn btn-primary" id="processBtn">✨ 全部反色</button>
<button class="btn btn-secondary" id="downloadAllBtn">📦 下载全部</button>
<button class="btn btn-danger" id="clearBtn">🗑️ 清空</button>
<span class="stats" id="statsDisplay">0 张图片</span>
</div>
<!-- 图片网格 -->
<div class="gallery" id="gallery">
<div class="empty-message">📸 添加图片后,将在此显示原始与反色预览</div>
</div>
<div class="footer-note">
<span>⚡ 反色 = 255 - 像素值 (RGB)</span>
<span>💡 点击单张下载按钮保存负片</span>
</div>
</div>
<script>
(function() {
// ---- DOM refs ----
const fileInput = document.getElementById('fileInput');
const dropZone = document.getElementById('dropZone');
const gallery = document.getElementById('gallery');
const processBtn = document.getElementById('processBtn');
const downloadAllBtn = document.getElementById('downloadAllBtn');
const clearBtn = document.getElementById('clearBtn');
const statsDisplay = document.getElementById('statsDisplay');
// ---- 数据存储 ----
// 每个条目: { id, file, originalDataUrl, invertedDataUrl (初始null) }
let imageItems = [];
let nextId = 0;
// ---- 辅助函数 ----
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / 1048576).toFixed(1) + ' MB';
}
function createCard(item) {
const card = document.createElement('div');
card.className = 'card';
card.dataset.id = item.id;
// 图片显示:优先显示反色后的,若没有则显示原始
const img = document.createElement('img');
img.className = 'card-image';
img.alt = 'preview';
// 使用 invertedDataUrl 或 originalDataUrl
img.src = item.invertedDataUrl || item.originalDataUrl;
const info = document.createElement('div');
info.className = 'card-info';
const filenameSpan = document.createElement('span');
filenameSpan.className = 'card-filename';
// 截断文件名
let name = item.file.name;
if (name.length > 18) {
name = name.substring(0, 12) + '…' + name.substring(name.lastIndexOf('.'));
}
filenameSpan.textContent = name;
const downloadBtn = document.createElement('button');
downloadBtn.className = 'card-download';
downloadBtn.textContent = '⬇ 负片';
downloadBtn.addEventListener('click', (e) => {
e.stopPropagation();
downloadSingleImage(item);
});
info.appendChild(filenameSpan);
info.appendChild(downloadBtn);
card.appendChild(img);
card.appendChild(info);
// 点击卡片预览大图? 简单起见:点击卡片触发下载单张
card.addEventListener('click', () => {
downloadSingleImage(item);
});
return card;
}
// 下载单张 (总是下载反色版本,若没有则下载原图)
function downloadSingleImage(item) {
const dataUrl = item.invertedDataUrl || item.originalDataUrl;
if (!dataUrl) return;
const link = document.createElement('a');
link.href = dataUrl;
// 添加负片后缀
const baseName = item.file.name.replace(/\.[^.]+$/, '');
const ext = item.file.name.includes('.') ? item.file.name.substring(item.file.name.lastIndexOf('.')) : '.png';
link.download = `${baseName}_负片${ext}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 更新 gallery 渲染
function renderGallery() {
gallery.innerHTML = '';
if (imageItems.length === 0) {
gallery.innerHTML = '<div class="empty-message">📸 添加图片后,将在此显示原始与反色预览</div>';
statsDisplay.textContent = '0 张图片';
return;
}
imageItems.forEach(item => {
const card = createCard(item);
gallery.appendChild(card);
});
statsDisplay.textContent = `${imageItems.length} 张图片`;
}
// ---- 核心反色函数 (使用Canvas) ----
function invertImage(item) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth || img.width;
canvas.height = img.naturalHeight || img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 获取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 反色: 255 - 值 (保留Alpha)
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // R
data[i + 1] = 255 - data[i + 1]; // G
data[i + 2] = 255 - data[i + 2]; // B
// data[i+3] 不变 (Alpha)
}
ctx.putImageData(imageData, 0, 0);
// 保存反色结果 (用 PNG 保证质量)
const invertedDataUrl = canvas.toDataURL('image/png');
item.invertedDataUrl = invertedDataUrl;
resolve(item);
};
img.onerror = () => {
// 如果加载失败,保持原样,标记反色为null
item.invertedDataUrl = null;
resolve(item);
};
img.src = item.originalDataUrl;
});
}
// 批量反色
async function batchInvert() {
if (imageItems.length === 0) return;
// 显示处理中状态 (简单禁用按钮)
processBtn.disabled = true;
processBtn.textContent = '⏳ 处理中...';
// 只处理还没有 invertedDataUrl 的(或者强制全部刷新? 这里全部重新生成保证一致性)
const promises = imageItems.map(item => invertImage(item));
await Promise.all(promises);
// 更新UI
renderGallery();
processBtn.disabled = false;
processBtn.textContent = '✨ 全部反色';
}
// ---- 添加文件 ----
function addFiles(files) {
if (!files || files.length === 0) return;
const validFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
if (validFiles.length === 0) {
alert('请选择图片文件 (JPG, PNG, WebP 等)');
return;
}
const newItems = [];
validFiles.forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
const originalDataUrl = e.target.result;
const item = {
id: nextId++,
file: file,
originalDataUrl: originalDataUrl,
invertedDataUrl: null, // 初始无反色
};
imageItems.push(item);
newItems.push(item);
// 如果是最后一张完成,渲染
if (newItems.length === validFiles.length) {
// 自动为新增图片执行反色 (也可手动触发)
// 为了更直观,自动反色
const autoInvert = async () => {
const invertPromises = newItems.map(item => invertImage(item));
await Promise.all(invertPromises);
renderGallery();
};
autoInvert();
}
};
reader.readAsDataURL(file);
});
// 清空 input 以便再次选择同一文件
fileInput.value = '';
}
// ---- 清空 ----
function clearAll() {
if (imageItems.length === 0) return;
if (confirm('确定清空所有图片吗?')) {
imageItems = [];
nextId = 0;
renderGallery();
}
}
// ---- 下载全部 (ZIP) ----
async function downloadAllAsZip() {
if (imageItems.length === 0) {
alert('没有图片可下载');
return;
}
// 动态加载 JSZip
let JSZip;
try {
const module = await import('https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js');
JSZip = module.default;
} catch (e) {
// fallback: 使用 script 加载
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
JSZip = window.JSZip;
}
const zip = new JSZip();
// 确保所有图片都有反色结果 (如果没有则先生成)
const needInvert = imageItems.filter(item => !item.invertedDataUrl);
if (needInvert.length > 0) {
processBtn.disabled = true;
processBtn.textContent = '⏳ 预处理...';
await Promise.all(needInvert.map(item => invertImage(item)));
processBtn.disabled = false;
processBtn.textContent = '✨ 全部反色';
renderGallery();
}
// 添加文件到 zip
imageItems.forEach(item => {
const dataUrl = item.invertedDataUrl || item.originalDataUrl;
if (!dataUrl) return;
// 将 dataURL 转为 blob
const byteString = atob(dataUrl.split(',')[1]);
const mimeString = dataUrl.split(',')[0].split(':')[1].split(';')[0];
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const blob = new Blob([ab], { type: mimeString });
const baseName = item.file.name.replace(/\.[^.]+$/, '');
const ext = '.png'; // 统一用 png 保持质量
zip.file(`${baseName}_负片${ext}`, blob);
});
const content = await zip.generateAsync({ type: 'blob' });
const link = document.createElement('a');
link.href = URL.createObjectURL(content);
link.download = `负片批量_${Date.now()}.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
}
// ---- 事件绑定 ----
// 点击上传区
dropZone.addEventListener('click', () => fileInput.click());
// 文件选择
fileInput.addEventListener('change', (e) => {
addFiles(e.target.files);
});
// 拖拽上传
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
addFiles(files);
}
});
// 按钮
processBtn.addEventListener('click', batchInvert);
clearBtn.addEventListener('click', clearAll);
downloadAllBtn.addEventListener('click', downloadAllAsZip);
// 额外:拖拽到页面其他位置阻止默认行为
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
// 初始渲染
renderGallery();
})();
</script>
</body>
</html>
批量图片反色工具
您可以通过拖拽或点击上传多张图片,一键生成负片效果,并支持批量下载。
- 图片上传与预览:您可以通过点击上传区域或直接拖拽图片文件到指定区域,工具会自动读取并显示图片,支持的图片格式包括 JPG、PNG、WebP 等常见格式。
- 核心反色功能:点击“全部反色”按钮,工具会使用 Canvas 技术对所有图片的像素值进行
255 - 原始值的计算,快速生成标准的负片效果,每张图片的处理是独立的,您可以随时对新增图片单独应用此功能。 - 灵活的下载选项:您可以点击单张图片下方的“负片”按钮或直接点击图片本身,下载该图片的反色版本,如需一次获取所有处理后的图片,点击“下载全部”按钮,工具会将所有负片图片打包成一个 ZIP 文件,方便您保存和分享。
标签: 负片效果
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播