图片批量反色工具负片效果

联启 电脑工具 16

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

图片批量反色工具负片效果-第1张图片-电脑手机工具软件下载 - 免费实用工具合集 | 联启科技

<!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 文件,方便您保存和分享。

标签: 负片效果

抱歉,评论功能暂时关闭!