欢迎光临升邦信息网
详情描述

一、完整实现代码

<!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>
        body {
            font-family: Arial, sans-serif;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        .container {
            display: flex;
            gap: 40px;
            flex-wrap: wrap;
        }
        .upload-section, .preview-section {
            flex: 1;
            min-width: 300px;
        }
        .control-panel {
            background: #f5f5f5;
            padding: 20px;
            border-radius: 8px;
            margin-bottom: 20px;
        }
        .control-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="range"] {
            width: 100%;
        }
        canvas {
            max-width: 100%;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .image-container {
            margin-top: 20px;
        }
        .file-info {
            background: #e9f7fe;
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        button {
            background: #007bff;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            margin-right: 10px;
        }
        button:hover {
            background: #0056b3;
        }
        .download-btn {
            background: #28a745;
        }
        .download-btn:hover {
            background: #1e7e34;
        }
        .processing {
            position: relative;
        }
        .processing::after {
            content: '处理中...';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0,0,0,0.8);
            color: white;
            padding: 10px 20px;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <h1>图片模糊和压缩处理</h1>

    <div class="container">
        <!-- 左侧:上传和控制面板 -->
        <div class="upload-section">
            <div class="control-panel">
                <div class="control-group">
                    <label for="fileInput">选择图片:</label>
                    <input type="file" id="fileInput" accept="image/*">
                </div>

                <div class="control-group">
                    <label for="blurRange">模糊程度:<span id="blurValue">0</span>px</label>
                    <input type="range" id="blurRange" min="0" max="20" value="0" step="1">
                </div>

                <div class="control-group">
                    <label for="qualityRange">压缩质量:<span id="qualityValue">80</span>%</label>
                    <input type="range" id="qualityRange" min="10" max="100" value="80" step="5">
                </div>

                <div class="control-group">
                    <label for="maxWidth">最大宽度:<span id="maxWidthValue">1000</span>px</label>
                    <input type="range" id="maxWidth" min="100" max="2000" value="1000" step="50">
                </div>

                <button id="processBtn">处理图片</button>
                <button id="resetBtn">重置</button>
                <button id="downloadBtn" class="download-btn" disabled>下载图片</button>
            </div>

            <div id="originalInfo" class="file-info" style="display: none;">
                <strong>原图信息:</strong>
                <div>大小:<span id="originalSize">-</span></div>
                <div>尺寸:<span id="originalDimensions">-</span></div>
            </div>

            <div class="image-container">
                <h3>原始图片预览:</h3>
                <canvas id="originalCanvas"></canvas>
            </div>
        </div>

        <!-- 右侧:处理后预览 -->
        <div class="preview-section">
            <div id="processedInfo" class="file-info" style="display: none;">
                <strong>处理后信息:</strong>
                <div>大小:<span id="processedSize">-</span></div>
                <div>尺寸:<span id="processedDimensions">-</span></div>
                <div>压缩率:<span id="compressionRate">-</span></div>
            </div>

            <div class="image-container">
                <h3>处理后图片:</h3>
                <canvas id="processedCanvas"></canvas>
            </div>
        </div>
    </div>

    <script>
        // 获取DOM元素
        const fileInput = document.getElementById('fileInput');
        const blurRange = document.getElementById('blurRange');
        const qualityRange = document.getElementById('qualityRange');
        const maxWidthInput = document.getElementById('maxWidth');
        const processBtn = document.getElementById('processBtn');
        const resetBtn = document.getElementById('resetBtn');
        const downloadBtn = document.getElementById('downloadBtn');

        const originalCanvas = document.getElementById('originalCanvas');
        const processedCanvas = document.getElementById('processedCanvas');

        const blurValue = document.getElementById('blurValue');
        const qualityValue = document.getElementById('qualityValue');
        const maxWidthValue = document.getElementById('maxWidthValue');

        const originalInfo = document.getElementById('originalInfo');
        const processedInfo = document.getElementById('processedInfo');

        // 状态变量
        let originalImage = null;
        let processedBlob = null;
        let originalSize = 0;

        // 事件监听器
        fileInput.addEventListener('change', handleFileSelect);
        blurRange.addEventListener('input', () => blurValue.textContent = blurRange.value);
        qualityRange.addEventListener('input', () => qualityValue.textContent = qualityRange.value);
        maxWidthInput.addEventListener('input', () => maxWidthValue.textContent = maxWidthInput.value);
        processBtn.addEventListener('click', processImage);
        resetBtn.addEventListener('click', resetAll);
        downloadBtn.addEventListener('click', downloadImage);

        // 处理文件选择
        function handleFileSelect(event) {
            const file = event.target.files[0];
            if (!file || !file.type.startsWith('image/')) {
                alert('请选择有效的图片文件!');
                return;
            }

            originalSize = file.size;
            showOriginalInfo(file);

            const reader = new FileReader();
            reader.onload = function(e) {
                originalImage = new Image();
                originalImage.onload = function() {
                    drawOriginalImage();
                    processBtn.disabled = false;
                };
                originalImage.src = e.target.result;
            };
            reader.readAsDataURL(file);
        }

        // 显示原图信息
        function showOriginalInfo(file) {
            originalInfo.style.display = 'block';
            document.getElementById('originalSize').textContent = formatFileSize(file.size);
        }

        // 绘制原始图片
        function drawOriginalImage() {
            const ctx = originalCanvas.getContext('2d');

            // 设置canvas尺寸
            originalCanvas.width = originalImage.width;
            originalCanvas.height = originalImage.height;

            // 清除并绘制
            ctx.clearRect(0, 0, originalCanvas.width, originalCanvas.height);
            ctx.drawImage(originalImage, 0, 0);

            // 更新尺寸信息
            document.getElementById('originalDimensions').textContent = 
                `${originalImage.width} × ${originalImage.height}`;
        }

        // 主处理函数
        async function processImage() {
            if (!originalImage) return;

            // 显示处理中状态
            processBtn.disabled = true;
            processBtn.classList.add('processing');

            try {
                // 获取参数
                const blurRadius = parseInt(blurRange.value);
                const quality = parseInt(qualityRange.value) / 100;
                const maxWidth = parseInt(maxWidthInput.value);

                // 计算新尺寸
                let newWidth = originalImage.width;
                let newHeight = originalImage.height;

                if (newWidth > maxWidth) {
                    newHeight = (maxWidth / newWidth) * newHeight;
                    newWidth = maxWidth;
                }

                // 设置处理后的canvas尺寸
                processedCanvas.width = newWidth;
                processedCanvas.height = newHeight;

                const ctx = processedCanvas.getContext('2d');

                // 1. 首先绘制缩放后的图片
                ctx.drawImage(originalImage, 0, 0, newWidth, newHeight);

                // 2. 应用模糊效果(如果模糊半径 > 0)
                if (blurRadius > 0) {
                    await applyBlur(ctx, newWidth, newHeight, blurRadius);
                }

                // 3. 转换为Blob并压缩
                processedCanvas.toBlob(
                    function(blob) {
                        processedBlob = blob;

                        // 显示处理后的信息
                        showProcessedInfo(blob, newWidth, newHeight);

                        // 启用下载按钮
                        downloadBtn.disabled = false;

                        // 移除处理中状态
                        processBtn.disabled = false;
                        processBtn.classList.remove('processing');
                    },
                    'image/jpeg',  // 可以改为 'image/png' 如果需要透明背景
                    quality
                );

            } catch (error) {
                console.error('处理图片时出错:', error);
                alert('处理图片时出错,请重试!');
                processBtn.disabled = false;
                processBtn.classList.remove('processing');
            }
        }

        // 应用模糊效果
        async function applyBlur(ctx, width, height, radius) {
            // 保存当前上下文
            ctx.save();

            // 方法1:使用CSS滤镜(简单但有限制)
            // 这种方法只适用于现代浏览器
            if (typeof ctx.filter !== 'undefined') {
                ctx.filter = `blur(${radius}px)`;
                ctx.clearRect(0, 0, width, height);
                ctx.drawImage(originalImage, 0, 0, width, height);
                ctx.filter = 'none';
            } 
            // 方法2:使用堆栈模糊算法(兼容性更好)
            else {
                await stackBlur(ctx, width, height, radius);
            }

            ctx.restore();
        }

        // 堆栈模糊算法(纯JavaScript实现)
        function stackBlur(ctx, width, height, radius) {
            return new Promise(resolve => {
                // 获取图像数据
                const imageData = ctx.getImageData(0, 0, width, height);
                const pixels = imageData.data;

                // 堆栈模糊算法实现
                const mul_table = [
                    512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
                    454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
                    482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
                    437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
                    497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
                    320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
                    446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
                    329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512
                ];

                const shg_table = [
                    9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
                    17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
                    19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
                    20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
                    21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
                    21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
                    22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
                    22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23
                ];

                function processChannel(channelOffset) {
                    const vmin = Math.min(width, height);
                    const div = 2 * radius + 1;
                    const w4 = width << 2;
                    const divsum = (div + 1) >> 1;
                    const divsumSq = divsum * divsum;

                    const stack = new Array(div * 3);
                    let stackpointer, stackstart;

                    for(let x = 0; x < width; x++) {
                        let sum = 0;
                        let sumout = 0;
                        let sumin = 0;
                        let yi = x;

                        for(let r = -radius; r <= radius; r++) {
                            let p = yi + ((r < 0 ? 0 : r >= height ? height-1 : r) * width);
                            stack[r+radius] = pixels[(p << 2) + channelOffset];
                            sum += stack[r+radius] * (radius + 1 - Math.abs(r));
                        }

                        stackpointer = radius;

                        for(let y = 0; y < height; y++) {
                            pixels[(yi << 2) + channelOffset] = sum / divsum;
                            yi += width;

                            sum -= sumout;
                            stackstart = stackpointer + div - radius;
                            if(stackstart >= div) stackstart -= div;
                            sumout = stack[stackstart];

                            let p = x + ((y + radius + 1 < height ? y + radius + 1 : height - 1) * width);
                            sumin = pixels[(p << 2) + channelOffset];
                            sum += sumin;

                            stack[stackstart] = sumin;
                            if(++stackpointer >= div) stackpointer = 0;
                        }
                    }

                    for(let y = 0; y < height; y++) {
                        let sum = 0;
                        let sumout = 0;
                        let sumin = 0;
                        let yi = y * width;

                        for(let r = -radius; r <= radius; r++) {
                            let p = yi + (r < 0 ? 0 : r >= width ? width-1 : r);
                            stack[r+radius] = pixels[(p << 2) + channelOffset];
                            sum += stack[r+radius] * (radius + 1 - Math.abs(r));
                        }

                        stackpointer = radius;

                        for(let x = 0; x < width; x++) {
                            pixels[(yi << 2) + channelOffset] = sum / divsum;
                            yi++;

                            sum -= sumout;
                            stackstart = stackpointer + div - radius;
                            if(stackstart >= div) stackstart -= div;
                            sumout = stack[stackstart];

                            let p = x + radius + 1 < width ? x + radius + 1 : width - 1;
                            p += y * width;
                            sumin = pixels[(p << 2) + channelOffset];
                            sum += sumin;

                            stack[stackstart] = sumin;
                            if(++stackpointer >= div) stackpointer = 0;
                        }
                    }
                }

                // 处理RGB三个通道
                processChannel(0); // R
                processChannel(1); // G
                processChannel(2); // B

                // 将处理后的数据放回canvas
                ctx.putImageData(imageData, 0, 0);
                resolve();
            });
        }

        // 显示处理后信息
        function showProcessedInfo(blob, width, height) {
            processedInfo.style.display = 'block';

            const processedSize = blob.size;
            const compressionRate = ((originalSize - processedSize) / originalSize * 100).toFixed(1);

            document.getElementById('processedSize').textContent = formatFileSize(processedSize);
            document.getElementById('processedDimensions').textContent = `${width} × ${height}`;
            document.getElementById('compressionRate').textContent = `${compressionRate}%`;
        }

        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }

        // 下载处理后的图片
        function downloadImage() {
            if (!processedBlob) return;

            const url = URL.createObjectURL(processedBlob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `processed_${Date.now()}.jpg`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }

        // 重置所有设置
        function resetAll() {
            // 重置控件
            blurRange.value = 0;
            qualityRange.value = 80;
            maxWidthInput.value = 1000;

            // 重置显示值
            blurValue.textContent = '0';
            qualityValue.textContent = '80';
            maxWidthValue.textContent = '1000';

            // 清除canvas
            const ctx1 = originalCanvas.getContext('2d');
            const ctx2 = processedCanvas.getContext('2d');
            ctx1.clearRect(0, 0, originalCanvas.width, originalCanvas.height);
            ctx2.clearRect(0, 0, processedCanvas.width, processedCanvas.height);

            // 重置状态
            originalCanvas.width = 1;
            originalCanvas.height = 1;
            processedCanvas.width = 1;
            processedCanvas.height = 1;

            // 隐藏信息面板
            originalInfo.style.display = 'none';
            processedInfo.style.display = 'none';

            // 禁用按钮
            processBtn.disabled = true;
            downloadBtn.disabled = true;

            // 清除文件输入
            fileInput.value = '';
            originalImage = null;
            processedBlob = null;
        }

        // 页面加载时初始化
        window.addEventListener('DOMContentLoaded', () => {
            resetAll();
        });
    </script>
</body>
</html>

二、核心功能实现原理

1. 图片压缩原理

// 核心压缩方法:canvas.toBlob()
canvas.toBlob(
    function(blob) {
        // blob就是压缩后的图片数据
    },
    'image/jpeg',  // 格式
    0.8            // 质量(0-1)
);

// 尺寸压缩:通过调整canvas大小
function resizeImage(image, maxWidth) {
    let width = image.width;
    let height = image.height;

    if (width > maxWidth) {
        height = (maxWidth / width) * height;
        width = maxWidth;
    }

    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(image, 0, 0, width, height);
}

2. 图片模糊原理

方法1:使用Canvas滤镜(推荐,现代浏览器)

ctx.filter = `blur(${radius}px)`;
ctx.drawImage(image, 0, 0, width, height);
ctx.filter = 'none';

方法2:堆栈模糊算法(兼容性好)

  • 对每个像素应用高斯模糊
  • 通过滑动窗口算法提高性能
  • 分别处理RGB通道

三、优化版本(支持Web Worker)

// blur-worker.js
self.onmessage = function(e) {
    const { imageData, width, height, radius } = e.data;
    const pixels = imageData.data;

    // 堆栈模糊算法实现...

    self.postMessage({ imageData });
};

主线程使用:

async function applyBlurWithWorker(ctx, width, height, radius) {
    const imageData = ctx.getImageData(0, 0, width, height);

    const worker = new Worker('blur-worker.js');
    worker.postMessage({ imageData, width, height, radius });

    worker.onmessage = function(e) {
        ctx.putImageData(e.data.imageData, 0, 0);
        worker.terminate();
    };
}

四、使用示例

// 1. 基本使用
const processor = new ImageProcessor();
await processor.loadImage(file);
await processor.process({
    blur: 5,
    quality: 0.7,
    maxWidth: 800
});
const blob = await processor.getBlob();

// 2. 批量处理
const files = [...]; // 多个文件
const promises = files.map(file => 
    new ImageProcessor().processFile(file, options)
);
const results = await Promise.all(promises);

五、注意事项

性能优化

  • 对大图片使用Web Worker
  • 限制处理图片的最大尺寸
  • 使用requestAnimationFrame进行渐进处理

兼容性处理

  • 检测canvas.filter支持情况
  • 提供polyfill或降级方案
  • 处理内存限制问题

用户体验

  • 显示处理进度
  • 提供取消操作
  • 错误处理和提示

安全考虑

  • 验证文件类型
  • 限制文件大小
  • 防止XSS攻击

这个实现完全在前端完成,不依赖任何后端服务,适合需要本地图片处理的场景。