分享一个自用的图片拼接工具

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>图片拼接工具...
分享一个自用的图片拼接工具
分享一个自用的图片拼接工具
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片拼接工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        }

        header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }

        header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
        }

        header p {
            opacity: 0.9;
            font-size: 1.1em;
        }

        .main-content {
            display: flex;
            gap: 30px;
            padding: 30px;
        }

        .left-panel {
            flex: 1;
            min-width: 300px;
        }

        .right-panel {
            flex: 2;
            min-width: 500px;
        }

        .section {
            background: #f8f9fa;
            border-radius: 15px;
            padding: 25px;
            margin-bottom: 20px;
        }

        .section h2 {
            color: #333;
            margin-bottom: 20px;
            font-size: 1.3em;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .section h2::before {
            content: '';
            width: 4px;
            height: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border-radius: 2px;
        }

        /* 上传区域 */
        .upload-area {
            border: 3px dashed #ddd;
            border-radius: 15px;
            padding: 40px 20px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s ease;
            background: white;
        }

        .upload-area:hover {
            border-color: #667eea;
            background: #f0f4ff;
        }

        .upload-area.dragover {
            border-color: #667eea;
            background: #e8eeff;
            transform: scale(1.02);
        }

        .upload-icon {
            font-size: 60px;
            margin-bottom: 15px;
        }

        .upload-text {
            color: #666;
            font-size: 1.1em;
        }

        .upload-hint {
            color: #999;
            font-size: 0.9em;
            margin-top: 10px;
        }

        input[type="file"] {
            display: none;
        }

        /* 布局选项 */
        .layout-options {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 15px;
        }

        .layout-option {
            background: white;
            border: 2px solid #e0e0e0;
            border-radius: 12px;
            padding: 15px;
            cursor: pointer;
            transition: all 0.3s ease;
            text-align: center;
        }

        .layout-option:hover {
            border-color: #667eea;
            transform: translateY(-2px);
        }

        .layout-option.active {
            border-color: #667eea;
            background: linear-gradient(135deg, #667eea10 0%, #764ba210 100%);
        }

        .layout-preview {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 50px;
            margin-bottom: 10px;
        }

        .layout-preview .box {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border-radius: 4px;
        }

        /* 上下排列 */
        .layout-v .box {
            width: 30px;
            height: 15px;
            margin: 2px 0;
        }

        /* 左右排列 */
        .layout-h .box {
            width: 15px;
            height: 30px;
            margin: 0 2px;
        }

        /* 2x2 网格 */
        .layout-2x2 {
            display: grid !important;
            grid-template-columns: repeat(2, 1fr);
            gap: 3px;
            width: fit-content;
        }

        .layout-2x2 .box {
            width: 18px;
            height: 18px;
        }

        /* 3x3 网格 */
        .layout-3x3 {
            display: grid !important;
            grid-template-columns: repeat(3, 1fr);
            gap: 2px;
            width: fit-content;
        }

        .layout-3x3 .box {
            width: 12px;
            height: 12px;
        }

        /* 4x4 网格 */
        .layout-4x4 {
            display: grid !important;
            grid-template-columns: repeat(4, 1fr);
            gap: 2px;
            width: fit-content;
        }

        .layout-4x4 .box {
            width: 10px;
            height: 10px;
        }

        .layout-name {
            font-weight: 600;
            color: #333;
            font-size: 0.95em;
        }

        /* 间距控制 */
        .gap-control {
            margin-top: 15px;
        }

        .gap-control label {
            display: block;
            margin-bottom: 10px;
            color: #555;
            font-weight: 500;
        }

        .gap-control input[type="range"] {
            width: 100%;
            height: 8px;
            border-radius: 4px;
            background: #e0e0e0;
            outline: none;
            -webkit-appearance: none;
        }

        .gap-control input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.2);
        }

        .gap-value {
            text-align: center;
            margin-top: 8px;
            color: #667eea;
            font-weight: 600;
        }

        /* 背景色控制 */
        .bg-control {
            margin-top: 20px;
        }

        .bg-control label {
            display: block;
            margin-bottom: 10px;
            color: #555;
            font-weight: 500;
        }

        .color-options {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }

        .color-option {
            width: 35px;
            height: 35px;
            border-radius: 8px;
            cursor: pointer;
            border: 3px solid transparent;
            transition: all 0.3s ease;
        }

        .color-option:hover {
            transform: scale(1.1);
        }

        .color-option.active {
            border-color: #333;
        }

        /* 图片列表 */
        .image-list {
            max-height: 300px;
            overflow-y: auto;
            padding-right: 10px;
        }

        .image-list::-webkit-scrollbar {
            width: 8px;
        }

        .image-list::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 4px;
        }

        .image-list::-webkit-scrollbar-thumb {
            background: #667eea;
            border-radius: 4px;
        }

        .image-item {
            display: flex;
            align-items: center;
            gap: 15px;
            background: white;
            padding: 12px;
            border-radius: 10px;
            margin-bottom: 10px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        }

        .image-item img {
            width: 60px;
            height: 60px;
            object-fit: cover;
            border-radius: 8px;
        }

        .image-info {
            flex: 1;
            overflow: hidden;
        }

        .image-name {
            font-weight: 600;
            color: #333;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .image-size {
            color: #888;
            font-size: 0.85em;
            margin-top: 3px;
        }

        .image-actions {
            display: flex;
            gap: 5px;
        }

        .btn-icon {
            width: 32px;
            height: 32px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.3s ease;
            font-size: 16px;
        }

        .btn-move {
            background: #e8f4fd;
            color: #2196F3;
        }

        .btn-move:hover {
            background: #2196F3;
            color: white;
        }

        .btn-delete {
            background: #ffebee;
            color: #f44336;
        }

        .btn-delete:hover {
            background: #f44336;
            color: white;
        }

        /* 预览区域 */
        .preview-area {
            background: #f5f5f5;
            border-radius: 15px;
            min-height: 400px;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: auto;
            padding: 20px;
        }

        .preview-placeholder {
            text-align: center;
            color: #999;
        }

        .preview-placeholder .icon {
            font-size: 80px;
            margin-bottom: 20px;
        }

        #previewCanvas {
            max-width: 100%;
            max-height: 600px;
            border-radius: 10px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }

        /* 按钮样式 */
        .btn {
            padding: 15px 30px;
            border: none;
            border-radius: 12px;
            font-size: 1.1em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            gap: 10px;
        }

        .btn-primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
        }

        .btn-primary:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
        }

        .btn-secondary {
            background: #f0f0f0;
            color: #333;
        }

        .btn-secondary:hover {
            background: #e0e0e0;
        }

        .button-group {
            display: flex;
            gap: 15px;
            margin-top: 20px;
            flex-wrap: wrap;
        }

        /* 空状态 */
        .empty-state {
            text-align: center;
            padding: 40px;
            color: #999;
        }

        .empty-state .icon {
            font-size: 60px;
            margin-bottom: 15px;
        }

        /* 响应式 */
        @media (max-width: 900px) {
            .main-content {
                flex-direction: column;
            }

            .left-panel, .right-panel {
                min-width: 100%;
            }
        }

        /* 动画 */
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .loading {
            animation: pulse 1.5s infinite;
        }

        /* 图片尺寸设置 */
        .size-control {
            margin-top: 20px;
        }

        .size-control label {
            display: block;
            margin-bottom: 10px;
            color: #555;
            font-weight: 500;
        }

        .size-inputs {
            display: flex;
            gap: 15px;
            align-items: center;
        }

        .size-input {
            flex: 1;
        }

        .size-input input {
            width: 100%;
            padding: 10px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 1em;
            transition: border-color 0.3s;
        }

        .size-input input:focus {
            outline: none;
            border-color: #667eea;
        }

        .size-separator {
            font-size: 1.2em;
            color: #999;
        }

        .size-locked {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-top: 10px;
        }

        .size-locked input[type="checkbox"] {
            width: 18px;
            height: 18px;
            cursor: pointer;
        }

        .size-locked label {
            cursor: pointer;
            margin: 0;
        }

        /* 输出格式选择 */
        .format-control {
            margin-top: 20px;
        }

        .format-control label {
            display: block;
            margin-bottom: 10px;
            color: #555;
            font-weight: 500;
        }

        .format-options {
            display: flex;
            gap: 10px;
        }

        .format-option {
            flex: 1;
            padding: 12px;
            background: white;
            border: 2px solid #e0e0e0;
            border-radius: 10px;
            cursor: pointer;
            text-align: center;
            transition: all 0.3s ease;
        }

        .format-option:hover {
            border-color: #667eea;
        }

        .format-option.active {
            border-color: #667eea;
            background: linear-gradient(135deg, #667eea10 0%, #764ba210 100%);
        }

        .format-option input {
            display: none;
        }

        .format-option span {
            font-weight: 600;
            color: #333;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>🖼️ 图片拼接工具</h1>
            <p>支持多种排列方式,无缝拼接,一键导出</p>
        </header>

        <div class="main-content">
            <div class="left-panel">
                <!-- 上传区域 -->
                <div class="section">
                    <h2>上传图片</h2>
                    <div class="upload-area" id="uploadArea">
                        <div class="upload-icon">📁</div>
                        <div class="upload-text">点击或拖拽图片到这里</div>
                        <div class="upload-hint">支持 JPG、PNG、GIF、WebP 格式</div>
                    </div>
                    <input type="file" id="fileInput" accept="image/*" multiple>
                </div>

                <!-- 布局设置 -->
                <div class="section">
                    <h2>布局方式</h2>
                    <div class="layout-options">
                        <div class="layout-option active" data-layout="vertical">
                            <div class="layout-preview layout-v">
                                <div class="box"></div>
                                <div class="box"></div>
                            </div>
                            <div class="layout-name">上 → 下</div>
                        </div>
                        <div class="layout-option" data-layout="horizontal">
                            <div class="layout-preview layout-h">
                                <div class="box"></div>
                                <div class="box"></div>
                            </div>
                            <div class="layout-name">左 → 右</div>
                        </div>
                        <div class="layout-option" data-layout="2x2">
                            <div class="layout-preview layout-2x2">
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                            </div>
                            <div class="layout-name">2 × 2</div>
                        </div>
                        <div class="layout-option" data-layout="3x3">
                            <div class="layout-preview layout-3x3">
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                            </div>
                            <div class="layout-name">3 × 3</div>
                        </div>
                        <div class="layout-option" data-layout="4x4">
                            <div class="layout-preview layout-4x4">
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                                <div class="box"></div>
                            </div>
                            <div class="layout-name">4 × 4</div>
                        </div>
                    </div>

                    <!-- 间距控制 -->
                    <div class="gap-control">
                        <label>图片间距:0 px(无缝拼接)</label>
                        <input type="range" id="gapRange" min="0" max="50" value="0">
                        <div class="gap-value" id="gapValue">0 px</div>
                    </div>

                    <!-- 背景色控制 -->
                    <div class="bg-control">
                        <label>背景颜色</label>
                        <div class="color-options">
                            <div class="color-option active" style="background: #ffffff;" data-color="#ffffff"></div>
                            <div class="color-option" style="background: #000000;" data-color="#000000"></div>
                            <div class="color-option" style="background: #f5f5f5;" data-color="#f5f5f5"></div>
                            <div class="color-option" style="background: #333333;" data-color="#333333"></div>
                            <div class="color-option" style="background: #ff6b6b;" data-color="#ff6b6b"></div>
                            <div class="color-option" style="background: #4ecdc4;" data-color="#4ecdc4"></div>
                            <div class="color-option" style="background: #667eea;" data-color="#667eea"></div>
                            <div class="color-option" style="background: #764ba2;" data-color="#764ba2"></div>
                        </div>
                    </div>

                    <!-- 输出格式 -->
                    <div class="format-control">
                        <label>输出格式</label>
                        <div class="format-options">
                            <label class="format-option active" data-format="png">
                                <input type="radio" name="format" value="png" checked>
                                <span>PNG</span>
                            </label>
                            <label class="format-option" data-format="jpeg">
                                <input type="radio" name="format" value="jpeg">
                                <span>JPEG</span>
                            </label>
                            <label class="format-option" data-format="webp">
                                <input type="radio" name="format" value="webp">
                                <span>WebP</span>
                            </label>
                        </div>
                    </div>
                </div>

                <!-- 图片列表 -->
                <div class="section">
                    <h2>图片列表 <span id="imageCount">(0)</span></h2>
                    <div class="image-list" id="imageList">
                        <div class="empty-state">
                            <div class="icon">📷</div>
                            <div>还没有添加图片</div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="right-panel">
                <div class="section" style="min-height: 500px;">
                    <h2>预览效果</h2>
                    <div class="preview-area" id="previewArea">
                        <div class="preview-placeholder" id="previewPlaceholder">
                            <div class="icon">🖼️</div>
                            <div>上传图片后预览拼接效果</div>
                        </div>
                        <canvas id="previewCanvas" style="display: none;"></canvas>
                    </div>
                    <div class="button-group">
                        <button class="btn btn-primary" id="mergeBtn" disabled>
                            🎨 生成拼接图片
                        </button>
                        <button class="btn btn-secondary" id="downloadBtn" disabled>
                            💾 下载图片
                        </button>
                        <button class="btn btn-secondary" id="clearBtn">
                            🗑️ 清空图片
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // 全局变量
        let images = [];
        let currentLayout = 'vertical';
        let gap = 0;
        let bgColor = '#ffffff';
        let outputFormat = 'png';
        let mergedImageData = null;

        // DOM 元素
        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('fileInput');
        const imageList = document.getElementById('imageList');
        const imageCount = document.getElementById('imageCount');
        const previewArea = document.getElementById('previewArea');
        const previewPlaceholder = document.getElementById('previewPlaceholder');
        const previewCanvas = document.getElementById('previewCanvas');
        const mergeBtn = document.getElementById('mergeBtn');
        const downloadBtn = document.getElementById('downloadBtn');
        const clearBtn = document.getElementById('clearBtn');
        const gapRange = document.getElementById('gapRange');
        const gapValue = document.getElementById('gapValue');

        // 上传事件
        uploadArea.addEventListener('click', () => fileInput.click());

        uploadArea.addEventListener('dragover', (e) => {
            e.preventDefault();
            uploadArea.classList.add('dragover');
        });

        uploadArea.addEventListener('dragleave', () => {
            uploadArea.classList.remove('dragover');
        });

        uploadArea.addEventListener('drop', (e) => {
            e.preventDefault();
            uploadArea.classList.remove('dragover');
            handleFiles(e.dataTransfer.files);
        });

        fileInput.addEventListener('change', (e) => {
            handleFiles(e.target.files);
            fileInput.value = '';
        });

        // 处理文件
        function handleFiles(files) {
            Array.from(files).forEach(file => {
                if (file.type.startsWith('image/')) {
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        const img = new Image();
                        img.onload = () => {
                            images.push({
                                id: Date.now() + Math.random(),
                                name: file.name,
                                size: file.size,
                                width: img.width,
                                height: img.height,
                                src: e.target.result,
                                img: img
                            });
                            updateUI();
                        };
                        img.src = e.target.result;
                    };
                    reader.readAsDataURL(file);
                }
            });
        }

        // 更新UI
        function updateUI() {
            imageCount.textContent = `(${images.length})`;
            mergeBtn.disabled = images.length === 0;

            if (images.length === 0) {
                imageList.innerHTML = `
                    <div class="empty-state">
                        <div class="icon">📷</div>
                        <div>还没有添加图片</div>
                    </div>
                `;
                previewPlaceholder.style.display = 'block';
                previewCanvas.style.display = 'none';
            } else {
                renderImageList();
                previewMerge();
            }
        }

        // 渲染图片列表
        function renderImageList() {
            imageList.innerHTML = images.map((img, index) => `
                <div class="image-item" data-id="${img.id}">
                    <img src="${img.src}" alt="${img.name}">
                    <div class="image-info">
                        <div class="image-name">${img.name}</div>
                        <div class="image-size">${img.width} × ${img.height} · ${formatSize(img.size)}</div>
                    </div>
                    <div class="image-actions">
                        <button class="btn-icon btn-move" onclick="moveImage(${index}, -1)" ${index === 0 ? 'disabled' : ''}>↑</button>
                        <button class="btn-icon btn-move" onclick="moveImage(${index}, 1)" ${index === images.length - 1 ? 'disabled' : ''}>↓</button>
                        <button class="btn-icon btn-delete" onclick="removeImage(${index})">×</button>
                    </div>
                </div>
            `).join('');
        }

        // 移动图片
        function moveImage(index, direction) {
            const newIndex = index + direction;
            if (newIndex < 0 || newIndex >= images.length) return;
            [images[index], images[newIndex]] = [images[newIndex], images[index]];
            renderImageList();
            previewMerge();
        }

        // 删除图片
        function removeImage(index) {
            images.splice(index, 1);
            updateUI();
        }

        // 格式化文件大小
        function formatSize(bytes) {
            if (bytes < 1024) return bytes + ' B';
            if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
            return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
        }

        // 布局选择
        document.querySelectorAll('.layout-option').forEach(option => {
            option.addEventListener('click', () => {
                document.querySelectorAll('.layout-option').forEach(o => o.classList.remove('active'));
                option.classList.add('active');
                currentLayout = option.dataset.layout;
                previewMerge();
            });
        });

        // 间距控制
        gapRange.addEventListener('input', (e) => {
            gap = parseInt(e.target.value);
            gapValue.textContent = gap + ' px';
            previewMerge();
        });

        // 背景色选择
        document.querySelectorAll('.color-option').forEach(option => {
            option.addEventListener('click', () => {
                document.querySelectorAll('.color-option').forEach(o => o.classList.remove('active'));
                option.classList.add('active');
                bgColor = option.dataset.color;
                previewMerge();
            });
        });

        // 输出格式选择
        document.querySelectorAll('.format-option').forEach(option => {
            option.addEventListener('click', () => {
                document.querySelectorAll('.format-option').forEach(o => o.classList.remove('active'));
                option.classList.add('active');
                outputFormat = option.dataset.format;
            });
        });

        // 预览拼接
        function previewMerge() {
            if (images.length === 0) {
                previewPlaceholder.style.display = 'block';
                previewCanvas.style.display = 'none';
                return;
            }

            const ctx = previewCanvas.getContext('2d');
            const result = calculateLayout();

            previewCanvas.width = result.canvasWidth;
            previewCanvas.height = result.canvasHeight;
            previewCanvas.style.display = 'block';
            previewPlaceholder.style.display = 'none';

            // 绘制背景
            ctx.fillStyle = bgColor;
            ctx.fillRect(0, 0, result.canvasWidth, result.canvasHeight);

            // 绘制图片
            result.positions.forEach(pos => {
                const img = images[pos.index];
                if (img && img.img) {
                    ctx.drawImage(img.img, pos.x, pos.y, pos.width, pos.height);
                }
            });

            mergedImageData = result;
        }

        // 计算布局
        function calculateLayout() {
            const positions = [];
            let canvasWidth = 0;
            let canvasHeight = 0;

            if (currentLayout === 'vertical') {
                // 上下排列 - 统一宽度
                const maxWidth = Math.max(...images.map(img => img.width));
                let y = 0;
                images.forEach((img, index) => {
                    const scale = maxWidth / img.width;
                    const width = maxWidth;
                    const height = img.height * scale;
                    positions.push({ index, x: 0, y, width, height });
                    y += height + gap;
                    canvasWidth = maxWidth;
                });
                canvasHeight = y - gap;

            } else if (currentLayout === 'horizontal') {
                // 左右排列 - 统一高度
                const maxHeight = Math.max(...images.map(img => img.height));
                let x = 0;
                images.forEach((img, index) => {
                    const scale = maxHeight / img.height;
                    const width = img.width * scale;
                    const height = maxHeight;
                    positions.push({ index, x, y: 0, width, height });
                    x += width + gap;
                });
                canvasWidth = x - gap;
                canvasHeight = maxHeight;

            } else {
                // 网格排列
                const gridSize = parseInt(currentLayout.split('x')[0]);
                const cellWidth = Math.max(...images.map(img => img.width));
                const cellHeight = Math.max(...images.map(img => img.height));

                images.forEach((img, index) => {
                    const row = Math.floor(index / gridSize);
                    const col = index % gridSize;
                    const x = col * (cellWidth + gap);
                    const y = row * (cellHeight + gap);
                    positions.push({ index, x, y, width: cellWidth, height: cellHeight });
                });

                const rows = Math.ceil(images.length / gridSize);
                canvasWidth = gridSize * cellWidth + (gridSize - 1) * gap;
                canvasHeight = rows * cellHeight + (rows - 1) * gap;
            }

            return { canvasWidth, canvasHeight, positions };
        }

        // 生成拼接图片
        mergeBtn.addEventListener('click', () => {
            previewMerge();
            downloadBtn.disabled = false;
        });

        // 下载图片
        downloadBtn.addEventListener('click', () => {
            if (!mergedImageData) return;

            const mimeType = `image/${outputFormat}`;
            const extension = outputFormat === 'jpeg' ? 'jpg' : outputFormat;
            const filename = `merged_${Date.now()}.${extension}`;

            const link = document.createElement('a');
            link.download = filename;
            link.href = previewCanvas.toDataURL(mimeType, 0.95);
            link.click();
        });

        // 清空图片
        clearBtn.addEventListener('click', () => {
            images = [];
            updateUI();
            downloadBtn.disabled = true;
        });

        // 初始加载提示
        console.log('🖼️ 图片拼接工具已就绪');
    </script>
</body>
</html>

现成的不能符合我的需求,我就Vibe了一个,需要的拿走不谢

1 个帖子 - 1 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文