图片批量修改自定义尺寸和颜色的背景(树洞先生)

为图片批量添加自定义尺寸和颜色的背景,支持多种格式

// ==UserScript==
// @name         图片批量修改自定义尺寸和颜色的背景(树洞先生)
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  为图片批量添加自定义尺寸和颜色的背景,支持多种格式
// @author       树洞先生
// @match        *://*/*
// @license      MIT
// @grant        none
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 创建工具界面
    function createToolInterface() {
        // 检查是否已存在工具
        if (document.getElementById('whiteBackgroundTool')) {
            return;
        }

        // 创建主容器
        const toolContainer = document.createElement('div');
        toolContainer.id = 'whiteBackgroundTool';
        toolContainer.innerHTML = `
            <div style="
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                width: 400px;
                background: white;
                border-radius: 8px;
                box-shadow: 0 4px 20px rgba(0,0,0,0.3);
                z-index: 10000;
                font-family: Arial, sans-serif;
                display: none;
            ">
                <div style="
                    background: #4CAF50;
                    color: white;
                    padding: 12px 16px;
                    border-radius: 8px 8px 0 0;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                ">
                    <div style="display: flex; align-items: center; gap: 8px;">
                        <span style="font-size: 16px;">✅</span>
                        <span style="font-weight: bold;">批量添加白色背景</span>
                    </div>
                    <button id="closeTool" style="
                        background: none;
                        border: none;
                        color: white;
                        font-size: 18px;
                        cursor: pointer;
                        padding: 0;
                        width: 24px;
                        height: 24px;
                    ">×</button>
                </div>

                <div style="padding: 16px;">
                    <!-- 功能说明 -->
                    <div style="margin-bottom: 16px;">
                        <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
                            <span style="font-size: 14px;">📋</span>
                            <span style="font-weight: bold; font-size: 14px;">功能说明:</span>
                        </div>
                        <ul style="margin: 0; padding-left: 20px; font-size: 13px; color: #666; line-height: 1.4;">
                            <li>创建自定义尺寸白色背景</li>
                            <li>图片自动居中放置</li>
                            <li>保持原图宽高比</li>
                            <li>支持JPG、PNG、BMP等格式</li>
                        </ul>
                    </div>

                    <!-- 自定义设置 -->
                    <div style="margin-bottom: 16px;">
                        <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
                            <span style="font-size: 14px;">⚙️</span>
                            <span style="font-weight: bold; font-size: 14px;">自定义设置:</span>
                        </div>

                        <div style="background: #f8f9fa; padding: 12px; border-radius: 6px; border-left: 3px solid #4CAF50;">
                            <!-- 尺寸设置 -->
                            <div style="display: flex; gap: 12px; margin-bottom: 10px;">
                                <div style="flex: 1;">
                                    <label style="display: block; margin-bottom: 4px; font-size: 12px; color: #666;">宽度 (px)</label>
                                    <input type="number" id="canvasWidth" value="800" min="100" max="4000" style="
                                        width: 100%;
                                        padding: 6px 8px;
                                        border: 1px solid #ddd;
                                        border-radius: 4px;
                                        font-size: 13px;
                                        box-sizing: border-box;
                                    ">
                                </div>
                                <div style="flex: 1;">
                                    <label style="display: block; margin-bottom: 4px; font-size: 12px; color: #666;">高度 (px)</label>
                                    <input type="number" id="canvasHeight" value="800" min="100" max="4000" style="
                                        width: 100%;
                                        padding: 6px 8px;
                                        border: 1px solid #ddd;
                                        border-radius: 4px;
                                        font-size: 13px;
                                        box-sizing: border-box;
                                    ">
                                </div>
                            </div>

                            <!-- 颜色设置 -->
                            <div>
                                <label style="display: block; margin-bottom: 4px; font-size: 12px; color: #666;">背景颜色</label>
                                <div style="display: flex; gap: 8px; align-items: center;">
                                    <input type="color" id="backgroundColor" value="#ffffff" style="
                                        width: 36px;
                                        height: 32px;
                                        border: 1px solid #ddd;
                                        border-radius: 4px;
                                        cursor: pointer;
                                        padding: 0;
                                    ">
                                    <input type="text" id="backgroundColorText" value="#ffffff" placeholder="#ffffff" style="
                                        flex: 1;
                                        padding: 6px 8px;
                                        border: 1px solid #ddd;
                                        border-radius: 4px;
                                        font-size: 13px;
                                        font-family: monospace;
                                        box-sizing: border-box;
                                    ">
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- 文件选择 -->
                    <div style="margin-bottom: 16px;">
                        <label style="
                            display: block;
                            margin-bottom: 8px;
                            font-weight: bold;
                            color: #333;
                            font-size: 14px;
                        ">选择图片文件:</label>
                        <input type="file" id="imageInput" multiple accept="image/*" style="
                            width: 100%;
                            padding: 8px;
                            border: 2px dashed #ddd;
                            border-radius: 5px;
                            cursor: pointer;
                            font-size: 13px;
                            box-sizing: border-box;
                        ">
                    </div>

                    <!-- 文件列表 -->
                    <div id="fileList" style="
                        max-height: 120px;
                        overflow-y: auto;
                        margin-bottom: 16px;
                        padding: 8px;
                        background: #f9f9f9;
                        border-radius: 5px;
                        font-size: 12px;
                        display: none;
                        border: 1px solid #eee;
                    "></div>

                    <!-- 操作按钮 -->
                    <div style="display: flex; gap: 10px;">
                        <button id="processImages" disabled style="
                            flex: 1;
                            padding: 10px;
                            background: #4CAF50;
                            color: white;
                            border: none;
                            border-radius: 5px;
                            cursor: pointer;
                            font-weight: bold;
                            font-size: 14px;
                        ">🚀 开始处理</button>

                        <button id="clearImages" disabled style="
                            padding: 10px 16px;
                            background: #ff4444;
                            color: white;
                            border: none;
                            border-radius: 5px;
                            cursor: pointer;
                            font-size: 14px;
                        ">🗑️ 清空</button>
                    </div>

                    <!-- 进度条 -->
                    <div id="progressContainer" style="
                        margin-top: 16px;
                        display: none;
                    ">
                        <div style="margin-bottom: 8px;">
                            <span id="progressText" style="font-size: 13px; color: #666;">处理中...</span>
                        </div>
                        <div style="
                            width: 100%;
                            height: 8px;
                            background: #eee;
                            border-radius: 4px;
                            overflow: hidden;
                        ">
                            <div id="progressBar" style="
                                height: 100%;
                                background: #4CAF50;
                                width: 0%;
                                transition: width 0.3s;
                            "></div>
                        </div>
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(toolContainer);

        // 绑定事件
        bindEvents();
    }

    // 绑定事件处理
    function bindEvents() {
        const toolPanel = document.querySelector('#whiteBackgroundTool > div');
        const closeBtn = document.getElementById('closeTool');
        const imageInput = document.getElementById('imageInput');
        const processBtn = document.getElementById('processImages');
        const clearBtn = document.getElementById('clearImages');
        const fileList = document.getElementById('fileList');

        // 背景设置相关元素
        const canvasWidth = document.getElementById('canvasWidth');
        const canvasHeight = document.getElementById('canvasHeight');
        const backgroundColor = document.getElementById('backgroundColor');
        const backgroundColorText = document.getElementById('backgroundColorText');

        let selectedFiles = [];

        // 关闭工具
        closeBtn.addEventListener('click', () => {
            toolPanel.style.display = 'none';
        });

        // 颜色选择器和文本框同步
        backgroundColor.addEventListener('input', (e) => {
            backgroundColorText.value = e.target.value;
        });

        backgroundColorText.addEventListener('input', (e) => {
            const color = e.target.value;
            if (/^#[0-9A-Fa-f]{6}$/.test(color)) {
                backgroundColor.value = color;
            }
        });

        // 文件选择
        imageInput.addEventListener('change', (e) => {
            selectedFiles = Array.from(e.target.files);
            updateFileList(selectedFiles);
            processBtn.disabled = selectedFiles.length === 0;
            clearBtn.disabled = selectedFiles.length === 0;
        });

        // 清空文件
        clearBtn.addEventListener('click', () => {
            selectedFiles = [];
            imageInput.value = '';
            updateFileList(selectedFiles);
            processBtn.disabled = true;
            clearBtn.disabled = true;
        });

        // 处理图片
        processBtn.addEventListener('click', () => {
            if (selectedFiles.length > 0) {
                const settings = {
                    width: parseInt(canvasWidth.value) || 800,
                    height: parseInt(canvasHeight.value) || 800,
                    backgroundColor: backgroundColor.value || '#ffffff'
                };
                processImages(selectedFiles, settings);
            }
        });

        // 更新文件列表显示
        function updateFileList(files) {
            if (files.length === 0) {
                fileList.style.display = 'none';
                return;
            }

            fileList.style.display = 'block';
            fileList.innerHTML = `
                <strong>已选择 ${files.length} 个文件:</strong><br>
                ${files.map((file, index) =>
                    `${index + 1}. ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`
                ).join('<br>')}
            `;
        }
    }

    // 处理图片主函数
    async function processImages(files, settings) {
        const progressContainer = document.getElementById('progressContainer');
        const progressBar = document.getElementById('progressBar');
        const progressText = document.getElementById('progressText');
        const processBtn = document.getElementById('processImages');

        // 显示进度条
        progressContainer.style.display = 'block';
        processBtn.disabled = true;

        const zip = new JSZip();
        const processedImages = [];

        for (let i = 0; i < files.length; i++) {
            const file = files[i];

            // 更新进度
            const progress = ((i + 1) / files.length) * 100;
            progressBar.style.width = progress + '%';
            progressText.textContent = `处理中... ${i + 1}/${files.length} - ${file.name}`;

            try {
                const processedImageBlob = await addWhiteBackground(file, settings);
                const fileName = getOutputFileName(file.name);

                zip.file(fileName, processedImageBlob);
                processedImages.push(fileName);

            } catch (error) {
                console.error(`处理文件 ${file.name} 时出错:`, error);
            }

            // 添加延迟,避免浏览器卡顿
            await new Promise(resolve => setTimeout(resolve, 100));
        }

        // 生成并下载ZIP文件
        progressText.textContent = '生成下载文件...';
        try {
            const zipBlob = await zip.generateAsync({type: 'blob'});
            const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, '');
            const zipName = `processed_images_${settings.width}x${settings.height}_${timestamp}.zip`;
            saveAs(zipBlob, zipName);

            progressText.textContent = `✅ 完成!已处理 ${processedImages.length} 张图片`;
            setTimeout(() => {
                progressContainer.style.display = 'none';
                processBtn.disabled = false;
            }, 3000);

        } catch (error) {
            console.error('生成ZIP文件出错:', error);
            progressText.textContent = '❌ 下载失败,请重试';
            processBtn.disabled = false;
        }
    }

    // 为单张图片添加背景
    function addWhiteBackground(file, settings) {
        return new Promise((resolve, reject) => {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            const img = new Image();

            img.onload = function() {
                // 设置画布尺寸
                canvas.width = settings.width;
                canvas.height = settings.height;

                // 填充背景颜色
                ctx.fillStyle = settings.backgroundColor;
                ctx.fillRect(0, 0, settings.width, settings.height);

                // 计算图片缩放比例(保持宽高比)
                const imgWidth = img.naturalWidth;
                const imgHeight = img.naturalHeight;
                const scale = Math.min(settings.width / imgWidth, settings.height / imgHeight);

                // 计算绘制尺寸和位置
                const drawWidth = imgWidth * scale;
                const drawHeight = imgHeight * scale;
                const x = (settings.width - drawWidth) / 2;
                const y = (settings.height - drawHeight) / 2;

                // 绘制图片(居中)
                ctx.drawImage(img, x, y, drawWidth, drawHeight);

                // 转换为Blob
                canvas.toBlob(resolve, 'image/jpeg', 0.95);
            };

            img.onerror = () => reject(new Error('图片加载失败'));

            // 读取文件
            const reader = new FileReader();
            reader.onload = (e) => {
                img.src = e.target.result;
            };
            reader.onerror = () => reject(new Error('文件读取失败'));
            reader.readAsDataURL(file);
        });
    }

    // 生成输出文件名
    function getOutputFileName(originalName) {
        const lastDotIndex = originalName.lastIndexOf('.');
        if (lastDotIndex === -1) {
            return `${originalName}_with_bg.jpg`;
        }

        const name = originalName.substring(0, lastDotIndex);
        return `${name}_with_bg.jpg`;
    }

    // 创建启动按钮
    function createLaunchButton() {
        const button = document.createElement('div');
        button.innerHTML = '🖼️';
        button.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            width: 50px;
            height: 50px;
            background: #4CAF50;
            color: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 20px;
            z-index: 9999;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
            transition: all 0.3s;
        `;

        button.addEventListener('mouseenter', () => {
            button.style.transform = 'scale(1.1)';
        });

        button.addEventListener('mouseleave', () => {
            button.style.transform = 'scale(1)';
        });

        button.addEventListener('click', () => {
            createToolInterface();
            const toolPanel = document.querySelector('#whiteBackgroundTool > div');
            toolPanel.style.display = toolPanel.style.display === 'none' ? 'block' : 'none';
        });

        document.body.appendChild(button);
    }

    // 等待页面加载完成后创建按钮
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createLaunchButton);
    } else {
        createLaunchButton();
    }

})();