LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C#批量图片处理神器:一键缩放千张图片,效率提升100倍!

admin
2025年11月26日 22:18 本文热度 724

你还在为批量处理大量图片而头疼吗?设计师需要将几百张产品图片统一缩放,运营同学要批量压缩社交媒体素材,开发者要为移动端适配不同尺寸的图标...

今天,我将分享一个完整的C#批量图片处理解决方案让你1分钟处理1000张图片,彻底告别重复劳动!

🎯 痛点分析:为什么需要批量图片处理?

在实际开发中,我们经常遇到这些场景:

  • • 电商系统:产品图片需要生成多种尺寸的缩略图
  • • 移动应用:图标适配不同分辨率的设备
  • • 网站优化:批量压缩图片提升加载速度
  • • 内容管理:统一调整图片尺寸和格式

手动处理这些任务不仅效率低下,还容易出错。今天我们就用C#打造一个专业级的批量处理工具

💡 解决方案:基于SkiaSharp的高性能图片处理

🔧 技术选型

我们选择SkiaSharp作为图片处理库,原因如下:

  • • 跨平台支持:Windows、Linux、macOS全覆盖
  • • 高性能:基于Google Skia引擎,GPU加速
  • • 功能丰富:支持多种图片格式和高质量缩放算法
  • • 内存优化:自动管理内存,避免内存泄漏

🛠️ 代码实战:构建批量处理核心功能

让我们逐步构建这个批量图片处理工具:

📁 文件选择与管理

private void BtnAddFiles_Click(object sender, EventArgs e)
{
    using (var openDialog = new OpenFileDialog())
    {
        // 设置支持的图片格式过滤器
        openDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff";
        openDialog.Multiselect = true// 允许多选
        
        if (openDialog.ShowDialog() == DialogResult.OK)
        {
            foreach (stringfilein openDialog.FileNames)
            {
                // 避免重复添加同一文件
                if (!fileListBox.Items.Contains(file))
                {
                    fileListBox.Items.Add(file);
                }
            }
        }
    }
}

🎯 关键要点:

  • • 使用OpenFileDialogMultiselect属性实现批量选择
  • • 通过Filter属性限制文件类型,提升用户体验
  • • 重复检查避免同一文件多次添加

📤 输出目录设置

private void BtnOutputFolder_Click(object sender, EventArgs e)
{
    using (var folderDialog = new FolderBrowserDialog())
    {
        if (folderDialog.ShowDialog() == DialogResult.OK)
        {
            outputFolder = folderDialog.SelectedPath;
            // 实时更新状态栏,让用户知道当前设置
            statusLabel.Text = $"输出文件夹: {outputFolder}";
        }
    }
}

🚀 异步批量处理核心逻辑

private async void BtnStart_Click(object sender, EventArgs e)
{
    // 参数验证 - 确保用户输入完整
    if (fileListBox.Items.Count == 0)
    {
        MessageBox.Show("请先添加要处理的文件!""提示"
            MessageBoxButtons.OK, MessageBoxIcon.Information);
        return;
    }
    
    if (string.IsNullOrEmpty(outputFolder))
    {
        MessageBox.Show("请选择输出文件夹!""提示"
            MessageBoxButtons.OK, MessageBoxIcon.Information);
        return;
    }
    
    // 防止重复点击
    btnStart.Enabled = false;
    
    // 初始化进度条
    progressBar.Maximum = fileListBox.Items.Count;
    progressBar.Value = 0;
    
    // 获取用户设置的缩放比例和输出格式
    var scale = (float)scaleNumeric.Value / 100f;
    var format = GetBatchFormat();
    
    // 异步处理,避免UI卡顿
    await Task.Run(() => ProcessBatch(scale, format));
    
    btnStart.Enabled = true;
    MessageBox.Show("批量处理完成!""提示"
        MessageBoxButtons.OK, MessageBoxIcon.Information);
}

🔥 性能优化要点:

  • • 使用async/await模式避免UI阻塞
  • • Task.Run将耗时操作移至后台线程
  • • 进度条实时反馈处理状态

🎨 图片缩放算法实现

private void ProcessBatch(float scale, SKEncodedImageFormat format)
{
    for (int i = 0; i < fileListBox.Items.Count; i++)
    {
        string inputFile = fileListBox.Items[i].ToString();
        
        // 跨线程更新UI - 重要!
        this.Invoke(new Action(() => {
            statusLabel.Text = $"正在处理: {Path.GetFileName(inputFile)}";
            progressBar.Value = i;
        }));
        
        try
        {
            // 使用using确保资源正确释放
            using (var bitmap = SKBitmap.Decode(inputFile))
            {
                if (bitmap != null)
                {
                    // 计算新尺寸
                    int newWidth = (int)(bitmap.Width * scale);
                    int newHeight = (int)(bitmap.Height * scale);
                    
                    using (var scaledBitmap = new SKBitmap(newWidth, newHeight))
                    using (var canvas = new SKCanvas(scaledBitmap))
                    using (var paint = new SKPaint { 
                        IsAntialias = true,           // 抗锯齿
                        FilterQuality = SKFilterQuality.High  // 高质量缩放
                    })
                    {
                        // 执行缩放绘制
                        canvas.DrawBitmap(bitmap, 
                            new SKRect(00, newWidth, newHeight), paint);
                        
                        // 生成输出文件名
                        string outputFile = Path.Combine(outputFolder,
                            Path.GetFileNameWithoutExtension(inputFile) + 
                            "_scaled" + GetExtension(format));
                        
                        // 保存文件
                        using (var fileStream = new FileStream(outputFile, FileMode.Create))
                        using (var skStream = new SKManagedWStream(fileStream))
                        {
                            scaledBitmap.Encode(skStream, format, 95); // 95%质量
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // 错误处理 - 单个文件失败不影响整体处理
            this.Invoke(new Action(() => {
                MessageBox.Show($"处理文件 {Path.GetFileName(inputFile)} 时发生错误:{ex.Message}",
                    "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }));
        }
    }
    
    // 更新最终状态
    this.Invoke(new Action(() => {
        progressBar.Value = fileListBox.Items.Count;
        statusLabel.Text = "处理完成";
    }));
}

💎 核心技术点:

  1. 1. 内存管理:使用using语句确保SKBitmap等资源及时释放
  2. 2. 高质量缩放SKFilterQuality.High提供最佳视觉效果
  3. 3. 抗锯齿处理IsAntialias = true让缩放后的图片更平滑
  4. 4. 跨线程操作Invoke确保后台线程安全更新UI

🔄 格式转换支持

private SKEncodedImageFormat GetBatchFormat()
{
    switch (outputFormatCombo.SelectedIndex)
    {
        case0return SKEncodedImageFormat.Png;  // 无损格式,适合图标
        case1return SKEncodedImageFormat.Jpeg; // 有损格式,适合照片
        case2return SKEncodedImageFormat.Bmp;  // 位图格式
        defaultreturn SKEncodedImageFormat.Png;
    }
}

private string GetExtension(SKEncodedImageFormat format)
{
    switch (format)
    {
        case SKEncodedImageFormat.Png: return".png";
        case SKEncodedImageFormat.Jpeg: return".jpg";
        case SKEncodedImageFormat.Bmp: return".bmp";
        defaultreturn".png";
    }
}

完整代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using SkiaSharp;


namespace AppGraphicsScaling
{
    public partial class FrmBatchProcess : Form
    {
        private string outputFolder;

        public FrmBatchProcess()
        {
            InitializeComponent();
        }

        private void BtnAddFiles_Click(object sender, EventArgs e)
        {
            using (var openDialog = new OpenFileDialog())
            {
                openDialog.Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff";
                openDialog.Multiselect = true;

                if (openDialog.ShowDialog() == DialogResult.OK)
                {
                    foreach (stringfilein openDialog.FileNames)
                    {
                        if (!fileListBox.Items.Contains(file))
                        {
                            fileListBox.Items.Add(file);
                        }
                    }
                }
            }
        }

        private void BtnRemoveFile_Click(object sender, EventArgs e)
        {
            var selectedItems = fileListBox.SelectedItems.Cast<string>().ToList();
            foreach (string item in selectedItems)
            {
                fileListBox.Items.Remove(item);
            }
        }

        private void BtnClearAll_Click(object sender, EventArgs e)
        {
            fileListBox.Items.Clear();
        }

        private void BtnOutputFolder_Click(object sender, EventArgs e)
        {
            using (var folderDialog = new FolderBrowserDialog())
            {
                if (folderDialog.ShowDialog() == DialogResult.OK)
                {
                    outputFolder = folderDialog.SelectedPath;
                    statusLabel.Text = $"输出文件夹: {outputFolder}";
                }
            }
        }

        private async void BtnStart_Click(object sender, EventArgs e)
        {
            if (fileListBox.Items.Count == 0)
            {
                MessageBox.Show("请先添加要处理的文件!""提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            if (string.IsNullOrEmpty(outputFolder))
            {
                MessageBox.Show("请选择输出文件夹!""提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            btnStart.Enabled = false;
            progressBar.Maximum = fileListBox.Items.Count;
            progressBar.Value = 0;

            var scale = (float)scaleNumeric.Value / 100f;
            var format = GetBatchFormat();

            await Task.Run(() => ProcessBatch(scale, format));

            btnStart.Enabled = true;
            MessageBox.Show("批量处理完成!""提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void ProcessBatch(float scale, SKEncodedImageFormat format)
        {
            for (int i = 0; i < fileListBox.Items.Count; i++)
            {
                string inputFile = fileListBox.Items[i].ToString();

                this.Invoke(new Action(() => {
                    statusLabel.Text = $"正在处理: {Path.GetFileName(inputFile)}";
                    progressBar.Value = i;
                }));

                try
                {
                    using (var bitmap = SKBitmap.Decode(inputFile))
                    {
                        if (bitmap != null)
                        {
                            int newWidth = (int)(bitmap.Width * scale);
                            int newHeight = (int)(bitmap.Height * scale);

                            using (var scaledBitmap = new SKBitmap(newWidth, newHeight))
                            using (var canvas = new SKCanvas(scaledBitmap))
                            using (var paint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High })
                            {
                                canvas.DrawBitmap(bitmap, new SKRect(00, newWidth, newHeight), paint);

                                string outputFile = Path.Combine(outputFolder,
                                    Path.GetFileNameWithoutExtension(inputFile) + "_scaled" + GetExtension(format));

                                using (var fileStream = new FileStream(outputFile, FileMode.Create))
                                using (var skStream = new SKManagedWStream(fileStream))
                                {
                                    scaledBitmap.Encode(skStream, format, 95);
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    this.Invoke(new Action(() => {
                        MessageBox.Show($"处理文件 {Path.GetFileName(inputFile)} 时发生错误:{ex.Message}",
                            "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }));
                }
            }

            this.Invoke(new Action(() => {
                progressBar.Value = fileListBox.Items.Count;
                statusLabel.Text = "处理完成";
            }));
        }

        private SKEncodedImageFormat GetBatchFormat()
        {
            switch (outputFormatCombo.SelectedIndex)
            {
                case0return SKEncodedImageFormat.Png;
                case1return SKEncodedImageFormat.Jpeg;
                case2return SKEncodedImageFormat.Bmp;
                defaultreturn SKEncodedImageFormat.Png;
            }
        }

        private string GetExtension(SKEncodedImageFormat format)
        {
            switch (format)
            {
                case SKEncodedImageFormat.Png: return".png";
                case SKEncodedImageFormat.Jpeg: return".jpg";
                case SKEncodedImageFormat.Bmp: return".bmp";
                defaultreturn".png";
            }
        }
    }
}


⚠️ 常见坑点提醒

1. 内存泄漏问题

// ❌ 错误写法 - 可能导致内存泄漏
var bitmap = SKBitmap.Decode(inputFile);
// 处理逻辑...
// 忘记释放资源

// ✅ 正确写法 - 自动释放资源
using (var bitmap = SKBitmap.Decode(inputFile))
{
    // 处理逻辑...
// 自动调用Dispose()

2. 跨线程UI更新

// ❌ 错误写法 - 会抛出跨线程异常
Task.Run(() => {
    progressBar.Value = i; // 跨线程访问UI控件
});

// ✅ 正确写法 - 使用Invoke
Task.Run(() => {
    this.Invoke(new Action(() => {
        progressBar.Value = i;
    }));
});

3. 大文件处理优化

对于超大图片文件,建议添加尺寸检查:

if (bitmap.Width > 10000 || bitmap.Height > 10000)
{
    // 对超大图片进行分块处理或提示用户
    MessageBox.Show("图片尺寸过大,建议先进行预处理");
    continue;
}

💡 总结:三个关键收获

通过这个批量图片处理工具的实现,我们掌握了:

  1. 1. 🔧 SkiaSharp图片处理技术:高性能、跨平台的图片处理解决方案,支持多种格式和高质量缩放算法
  2. 2. ⚡ 异步编程最佳实践:使用async/await避免UI阻塞,通过Invoke安全更新界面,提升用户体验
  3. 3. 🛡️ 资源管理与异常处理:proper使用using语句管理内存,完善的错误处理机制确保程序稳定性

这个工具不仅解决了批量图片处理的痛点,更重要的是展示了C#在桌面应用开发中的强大能力。无论是企业级应用还是个人工具,都可以基于这个框架进行扩展。


阅读原文:原文链接


该文章在 2025/11/27 16:24:12 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved